Skip to content

Move batch authentication to derivation pipeline, remove BatchInbox#357

Merged
QuentinI merged 13 commits intocelo-integration-rebase-14.2from
ag/move-batch-auth-to-derivation-pipeline
Mar 20, 2026
Merged

Move batch authentication to derivation pipeline, remove BatchInbox#357
QuentinI merged 13 commits intocelo-integration-rebase-14.2from
ag/move-batch-auth-to-derivation-pipeline

Conversation

@QuentinI
Copy link
Copy Markdown
Collaborator

@QuentinI QuentinI commented Feb 20, 2026

This PR:

  • Moves batch authentication from on-chain to the derivation pipeline. Now the derivation pipeline scans L1 receipts for BatchInfoAuthenticated events in a 100-block lookback window, compatible with op-program fault proofs.

  • Removes the BatchInbox contract entirely.

  • Removes the validBatchInfo mapping from BatchAuthenticator. The contract no longer needs to store batch info for on-chain validation — it only needs to emit the BatchInfoAuthenticated event.

  • Adds FallbackBatcherAddress for dual-mode auth. The TEE batcher must have a matching BatchInfoAuthenticated event. The fallback (non-TEE) batcher doesn't call authenticateBatchInfo, so it's authorized via sender verification against a configured FallbackBatcherAddress. This fixes the derivation pipeline rejecting all fallback batcher transactions.

This PR does not:

  • Update the op-succinct fork to accept fallback_batcher_address in rollup config JSON — tracked separately. The succinct-proposer Rust deserializer rejects this unknown field, causing devnet test failures.

Key places to review:

  • op-node/rollup/derive/batch_authenticator.go — New file: event scanning logic (CollectAuthenticatedBatches, ComputeCalldataBatchHash, ComputeBlobBatchHash)
  • op-node/rollup/derive/data_source.goisBatchTxAuthorized() dual-mode auth (event-based for TEE, sender-based for fallback)
  • packages/contracts-bedrock/src/L1/BatchAuthenticator.sol — Storage removal (validBatchInfo mapping removed)

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @QuentinI, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the batch submission and authentication process by eliminating the dedicated BatchInbox smart contract. Instead, the BatchInbox address now functions as an EOA, simplifying the L1 contract landscape. Batch authentication is now handled off-chain by the derivation pipeline, which actively monitors BatchAuthenticator events on L1 to validate incoming batches. This change streamlines the architecture and moves towards a more event-centric model for processing batches.

Highlights

  • BatchInbox Contract Removal: The BatchInbox.sol smart contract has been removed. The BatchInbox address is now treated as an Externally Owned Account (EOA).
  • Off-chain Batch Authentication: Batch authentication logic has been shifted from on-chain BatchInbox contract delegation to off-chain derivation pipeline monitoring. The pipeline now scans L1 receipts for BatchInfoAuthenticated events emitted by the BatchAuthenticator contract.
  • Updated Go Bindings and Deployment Scripts: Go bindings for the removed BatchInbox contract have been deleted, and the BatchAuthenticator bindings have been updated. Deployment scripts and configuration files have been adjusted to reflect the new EOA BatchInbox and the updated authentication flow.
  • Derivation Pipeline Logic Refinement: The derivation pipeline's data sources (CalldataSource and BlobDataSource) have been updated to incorporate the new event-based batch authentication mechanism, including a lookback window for BatchInfoAuthenticated events.
  • Test Updates: Existing tests and new test files have been added or modified to align with the EOA BatchInbox and the event-driven authentication, ensuring correct behavior and error handling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • AGENTS.md
    • Removed mention of BatchInbox.sol from the Espresso integration table.
    • Updated Go binding regeneration instructions to remove BatchInbox.
  • docs/CELO_TESTNET_MIGRATION.md
    • Removed BatchInbox from the list of new L1 contracts Celo needs to determine.
  • espresso/SECURITY_ANALYSIS.md
    • Updated test name from TestChangeBatchInboxOwner to TestChangeBatchAuthenticatorOwner.
  • espresso/devnet-tests/key_rotation_test.go
    • Renamed test function TestChangeBatchInboxOwner to TestChangeBatchAuthenticatorOwner.
  • espresso/environment/3_2_espresso_deterministic_state_test.go
    • Updated comments to reflect BatchInbox as an EOA.
    • Changed receipt expectation from ForReceiptFail to ForReceiptOK for transactions to the BatchInbox EOA.
  • espresso/environment/5_batch_authentication_test.go
    • Updated comments and error messages to refer to BatchAuthenticator instead of batch inbox contract.
  • espresso/environment/6_batch_inbox_test.go
    • Rewrote test comments to clarify BatchInbox is an EOA and batches are rejected by the derivation pipeline if unauthenticated.
    • Updated assertions to expect successful L1 transaction status for batches sent to the EOA BatchInbox.
  • espresso/environment/9_pipeline_enhancement_test.go
    • Rewrote test comments to explain that the derivation pipeline rejects batches without a BatchInfoAuthenticated event.
    • Updated receipt expectation from ForReceiptFail to ForReceiptMaybe with ReceiptStatusSuccessful for transactions to the BatchInbox EOA.
  • justfile
    • Removed the command to generate Go bindings for BatchInbox.sol.
  • op-batcher/batcher/driver.go
    • Updated error message to refer to BatchAuthenticator instead of batch inbox contract.
  • op-batcher/batcher/espresso.go
    • Updated log messages to refer to BatchAuthenticator instead of batch inbox contract.
  • op-batcher/bindings/batch_authenticator.go
    • Removed validateBatch, validateNonTeeBatch, and validateTeeBatch functions from the Go bindings.
    • Updated ABI and Bin metadata for BatchAuthenticator.
  • op-batcher/bindings/batch_inbox.go
    • Removed the entire file, which contained Go bindings for BatchInbox.sol.
  • op-batcher/bindings/opsuccinct_fault_dispute_game.go
    • Updated ABI and Bin metadata.
  • op-chain-ops/genesis/config.go
    • Removed BatchInbox field from L1Deployments struct.
    • Updated Check method to no longer reference BatchInbox.
  • op-deployer/pkg/deployer/inspect/l1.go
    • Removed BatchInboxAddress from OpChainDeployment struct.
    • Removed BatchInbox from the AsL1Deployments mapping.
  • op-deployer/pkg/deployer/opcm/espresso.go
    • Removed BatchInboxAddress from DeployEspressoOutput struct.
  • op-deployer/pkg/deployer/pipeline/espresso.go
    • Updated log message for Espresso deployment to reflect BatchAuthenticator.
    • Removed assignment of BatchInboxAddress from chainState.
  • op-deployer/pkg/deployer/state/deploy_config.go
    • Updated calculateBatchInboxAddr to directly compute the EOA address based on chainID.
    • Removed conditional check for existing BatchInboxAddress in calculateBatchInboxAddr.
  • op-deployer/pkg/deployer/state/state.go
    • Removed BatchInboxAddress from ChainState struct.
  • op-node/rollup/derive/altda_data_source_test.go
    • Modified test logic to reflect changes in InfoAndTxsByHash and receipt fetching for error scenarios with the new EOA BatchInbox.
  • op-node/rollup/derive/batch_authenticator.go
    • Added new file defining constants and helper functions for event-based batch authentication.
    • Introduced BatchAuthLookbackWindow constant and BatchInfoAuthenticatedABIHash.
    • Added functions ComputeCalldataBatchHash, ComputeBlobBatchHash, FindBatchAuthEvent, and CollectAuthenticatedBatches.
  • op-node/rollup/derive/batch_authenticator_test.go
    • Added new test file for batch_authenticator.go covering hash computation, event finding, and batch collection.
  • op-node/rollup/derive/blob_data_source.go
    • Updated NewBlobDataSource to accept L1Fetcher instead of L1TransactionFetcher.
    • Modified dataAndHashesFromTxs to incorporate event-based authentication logic, removing reliance on transaction receipts for status and adding calls to CollectAuthenticatedBatches.
  • op-node/rollup/derive/blob_data_source_test.go
    • Updated tests to remove receipt-based checks.
    • Introduced new tests for event-based authentication for blob transactions.
  • op-node/rollup/derive/calldata_source.go
    • Updated NewCalldataSource to accept L1Fetcher instead of L1TransactionFetcher.
    • Modified DataFromEVMTransactions to incorporate event-based authentication logic, removing reliance on transaction receipts for status and adding calls to CollectAuthenticatedBatches.
  • op-node/rollup/derive/calldata_source_test.go
    • Updated tests to remove receipt-based checks.
    • Introduced new tests for event-based authentication for calldata transactions.
  • op-node/rollup/derive/data_source.go
    • Updated DataSourceConfig to include batchAuthenticatorAddress.
    • Added BatchAuthEnabled method to DataSourceConfig.
    • Modified isValidBatchTx to remove receipt status check and sender verification.
    • Introduced isAuthorizedBatchSender and isBatchTxAuthorized for authentication logic.
  • packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol
    • Removed the validateBatch function.
  • packages/contracts-bedrock/interfaces/L1/IBatchInbox.sol
    • Removed the entire file.
  • packages/contracts-bedrock/scripts/checks/interfaces/main.go
    • Removed IBatchInbox from the exclusion list.
  • packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol
    • Removed deployment logic for BatchInbox.
    • Updated DeployEspressoOutput struct and checkOutput function to no longer reference BatchInbox.
  • packages/contracts-bedrock/snapshots/abi/BatchInbox.json
    • Removed the ABI snapshot for BatchInbox.sol.
  • packages/contracts-bedrock/snapshots/storageLayout/BatchInbox.json
    • Removed the storage layout snapshot for BatchInbox.sol.
  • packages/contracts-bedrock/src/L1/BatchAuthenticator.sol
    • Removed Strings import.
    • Removed validateTeeBatch, validateNonTeeBatch, and validateBatch functions.
    • Added a comment explaining the new off-chain authentication mechanism.
  • packages/contracts-bedrock/src/L1/BatchInbox.sol
    • Removed the entire file.
  • packages/contracts-bedrock/test/L1/BatchInbox.t.sol
    • Removed the entire test file.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/espresso-devnet-tests.yaml
Activity
  • The pull request is currently marked as "[WIP]", indicating ongoing development.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the batch authentication mechanism by removing the BatchInbox smart contract and shifting its validation logic off-chain to the derivation pipeline. The new approach relies on the BatchAuthenticator contract emitting BatchInfoAuthenticated events, which the op-node then scans for within a defined lookback window to authorize batches. The changes introduce new Go functions to compute batch hashes and collect these authentication events from L1 receipts, updating calldata_source and blob_data_source to use this event-based authentication. Review comments highlight a potential performance bottleneck and reorg-safety concern in the CollectAuthenticatedBatches implementation, suggesting a more efficient eth_getLogs approach, and raise a liveness risk due to the fixed BatchAuthLookbackWindow potentially being too short during periods of high L1 congestion.

Comment on lines +119 to +131
for blockNum := startBlock; blockNum <= ref.Number; blockNum++ {
blockRef, err := fetcher.L1BlockRefByNumber(ctx, blockNum)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("batch auth: failed to fetch L1 block ref %d: %w", blockNum, err))
}
_, receipts, err := fetcher.FetchReceipts(ctx, blockRef.Hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("batch auth: failed to fetch receipts for block %d: %w", blockNum, err))
}
for h := range collectAuthEventsFromReceipts(receipts, authenticatorAddr) {
allAuthenticated[h] = true
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This loop introduces a severe performance bottleneck. For every L1 block processed, it performs up to 100 iterations, each making two RPC calls (L1BlockRefByNumber and FetchReceipts). This results in ~200 RPC calls per L1 block, which will likely cause the node to fall behind during synchronization. Furthermore, L1BlockRefByNumber is not reorg-safe as it retrieves the block currently at that height, which may not belong to the canonical chain being derived. It is highly recommended to use eth_getLogs with a block range to retrieve all relevant events in a single, efficient, and reorg-safe RPC call.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of L1BlockRefByNumber seems fine to me.

Re: the iterations, can we cache the allAuthenticated set to avoid iterating 100 times for each consecutive block?

@QuentinI QuentinI force-pushed the ag/move-batch-auth-to-derivation-pipeline branch from 7c2d257 to 6051341 Compare February 20, 2026 17:35
@QuentinI QuentinI changed the title [WIP] EOA BatchInbox Move batch authentication to derivation pipeline, remove BatchInbox Feb 20, 2026
Comment on lines +119 to +131
for blockNum := startBlock; blockNum <= ref.Number; blockNum++ {
blockRef, err := fetcher.L1BlockRefByNumber(ctx, blockNum)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("batch auth: failed to fetch L1 block ref %d: %w", blockNum, err))
}
_, receipts, err := fetcher.FetchReceipts(ctx, blockRef.Hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("batch auth: failed to fetch receipts for block %d: %w", blockNum, err))
}
for h := range collectAuthEventsFromReceipts(receipts, authenticatorAddr) {
allAuthenticated[h] = true
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of L1BlockRefByNumber seems fine to me.

Re: the iterations, can we cache the allAuthenticated set to avoid iterating 100 times for each consecutive block?

Comment on lines +181 to +182
// Legacy mode: verify sender
return isAuthorizedBatchSender(tx, dsCfg.l1Signer, batcherAddr, logger)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we require authenticatedHashes to be not nil for the TEE batcher, and only check isAuthorizedBatchSender for the fallback batcher, to avoid the TEE batcher not providing the hash?

//
// At ~12s per L1 block, 100 blocks ≈ 20 minutes. This gives the batcher ample time
// to land the batch data transaction on L1 after the authentication transaction,
// even under moderate L1 congestion or batcher restarts. The window is intentionally
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does moderate mean in that case? What could happen in the case of a re-org for example?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does moderate mean in that case?

Nothing in particular, this comment was co-written by Claude so it's a bit pretentious in wording. I'll re-write to make it sound less like I made a formal study of L1 congestion before landing on the 100 value for the lookback window.

What could happen in the case of a re-org for example?

If we're traversing the lookback window mid-reorg, we could run into inconsistent state, but the derivation pipeline is reset on reorgs, so the issue would be transient.

}
}

if len(allAuthenticated) > 0 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: return 0 authenticated batchs, can be a debug log.

@QuentinI QuentinI force-pushed the ag/move-batch-auth-to-derivation-pipeline branch from 7c2e7cb to 3765a04 Compare March 2, 2026 08:09
@QuentinI
Copy link
Copy Markdown
Collaborator Author

QuentinI commented Mar 2, 2026

Re: the iterations, can we cache the allAuthenticated set to avoid iterating 100 times for each consecutive block?

Yeah, that's a good idea. Added an LRU cache.

@QuentinI QuentinI force-pushed the ag/move-batch-auth-to-derivation-pipeline branch from 834dc67 to 2a3142d Compare March 2, 2026 17:28
QuentinI and others added 5 commits March 3, 2026 19:37
… contract

Move batch authentication from on-chain validation (BatchInbox contract calling
BatchAuthenticator.validateBatch) to off-chain verification in the derivation
pipeline via BatchInfoAuthenticated event scanning.

Key changes:
- Add batch_authenticator.go: event-based auth using CollectAuthenticatedBatches
  which scans a lookback window once per L1 block and returns authenticated hashes
- Add isBatchTxAuthorized helper shared by calldata and blob data sources
- Remove BatchInbox contract, interface, tests, bindings, and snapshots entirely
  (Espresso-introduced, never in Celo fork)
- Remove validateBatch/validateTeeBatch/validateNonTeeBatch from BatchAuthenticator
  (authenticateBatchInfo and signer management remain)
- Regenerate BatchAuthenticator Go bindings
- Revert BatchInbox-related fields from L1Deployments, ChainState, DeployEspressoOutput,
  and calculateBatchInboxAddr back to upstream signatures (introduce+remove no-ops)
- Update integration/devnet tests for EOA BatchInbox behavior

Co-authored-by: OpenCode <noreply@opencode.ai>
… auth

When batch authentication is enabled, the TEE batcher requires a matching
BatchInfoAuthenticated event on L1. The fallback (non-TEE) batcher does not
call authenticateBatchInfo, so its transactions had no auth events and were
rejected by the derivation pipeline.

Add FallbackBatcherAddress to rollup.Config and the derivation pipeline's
DataSourceConfig. When event-based auth finds no matching event for a batch,
it falls back to sender verification against the fallback batcher address.
This allows the non-TEE batcher to post batches without on-chain auth events
while still requiring event-based auth for the TEE batcher.

Co-authored-by: OpenCode <noreply@opencode.ai>
The validBatchInfo mapping was written to on every authenticateBatchInfo
call but no longer read by the derivation pipeline, which now uses
BatchInfoAuthenticated events instead. Removing it saves ~20k gas per
call.

- Remove mapping declaration and write from BatchAuthenticator.sol
- Remove from IBatchAuthenticator interface
- Remove test assertions on validBatchInfo
- Update SECURITY_ANALYSIS.md to reflect event-based auth model
- Bump contract version to 1.1.0
- Regenerate ABI/storage snapshots and Go bindings

Co-authored-by: OpenCode <noreply@opencode.ai>
@QuentinI QuentinI force-pushed the ag/move-batch-auth-to-derivation-pipeline branch from 2a3142d to 9451342 Compare March 3, 2026 18:45
Comment on lines +166 to +179
if dsCfg.BatchAuthEnabled() {
// Event-based authentication: TEE batcher must have an auth event
if authenticatedHashes[batchHash] {
return true
}
// Fallback batcher: accept via sender verification
if dsCfg.fallbackBatcherAddress != (common.Address{}) {
if isAuthorizedBatchSender(tx, dsCfg.l1Signer, dsCfg.fallbackBatcherAddress, logger) {
return true
}
}
logger.Warn("batch not authenticated via event or fallback sender",
"txHash", tx.Hash(), "batchHash", batchHash)
return false
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's removing the need for a switch batcher step? If so can the switching functionality be removed from the contract?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Batchers may post simultaneously, it won't break anything; but they still shouldn't, so batchers poll it here: https://github.com/EspressoSystems/optimism-espresso-integration/pull/357/changes#diff-c734d1296b2fd691221b92df3edf09c7533c507a74c2316117745c75c3ad5776R814

CaffNodeConfig espresso.CLIConfig `json:"caff_node_config,omitempty"`

BatchAuthenticatorAddress common.Address `json:"batch_authenticator_address,omitempty,omitzero"`
FallbackBatcherAddress common.Address `json:"fallback_batcher_address,omitempty,omitzero"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we drop this address and just use the existing batcher address? I believe this would also simplify the isBatchTxAuthorized implementation since it would avoid a second callsite for isAuthorizedBatchSender

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we drop this address and just use the existing batcher address

Certainly, but it would be an extensive change due to all the wiring outside of derivation pipeline assuming the existing batcher address is the TEE batcher. I'm not opposed to making that change in the future, but would prefer to leave it out of scope of this PR.

cc @philippecamacho @shenkeyao what do you think?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @QuentinI, could you explain a bit what and where this external wiring is? At least from within the derivation pipeline there doesn't seem to be any assumption that the existing batcher address is the TEE batcher.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, forgot to answer your question. Here's a PR based on this branch that does just that: #370

batchAuthCacheOnce.Do(func() {
// Size slightly larger than the lookback window to avoid thrashing
// at the boundary. lru.New only errors on size <= 0.
batchAuthCache, _ = lru.New[common.Hash, map[common.Hash]bool](int(BatchAuthLookbackWindow) + 16)
Copy link
Copy Markdown

@piersy piersy Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think + 2 would be sufficient here instead of + 16. The lookback window covers 101 blocks (the ref block plus 100 ancestors), so 101 slots seems like the natural size. But the traversal reads from newest to oldest, which means the ref block is touched first and becomes the LRU entry. With exactly 101 slots, inserting the next ref block evicts the previous ref block (its parent), which we're about to read — causing a cascade of evict-and-refetch through the entire window.

With 102 slots, the cache has room for the 101 new window entries plus one stale entry (the block that just fell out of the lookback window). That stale entry is the LRU since it wasn't touched in the current traversal, so it's the one that gets evicted — no cascade

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fair, I was being conservative. I'll push it down to +2.

QuentinI and others added 5 commits March 5, 2026 14:21
The fallback batcher (op-batcher-fallback) was using Anvil account #3
(0x90F79bf6EB2c4f870365E785982E1f101E93b906), which is also the Deployer
key used by devnet tests for admin transactions (SwitchBatcher,
TransferOwnership, etc.). When both the fallback batcher service and test
code sign L1 transactions from the same account concurrently, nonce
collisions cause transactions to never be mined, leading to timeouts.

Switch the fallback batcher to Anvil account #6
(0x976EA74026E726554dB657fA54763abd0C3a0aa9) across docker-compose,
prepare-allocs (nonTeeBatcher in rollup config), and .env.

Co-authored-by: OpenCode <noreply@opencode.ai>
Query BatchAuthenticator.activeIsTee() before publishing to L1.
The inactive batcher (TEE or fallback) now skips publishStateToL1
instead of posting transactions that would be ignored by the
derivation pipeline.

This was previously unnecessary because the BatchInbox contract's
validateBatch() would revert inactive batcher transactions during
eth_estimateGas. With the move to an EOA batch inbox, both batchers
can freely post, so an explicit idle check is needed.

Co-authored-by: OpenCode <noreply@opencode.ai>
…tes32,address)

PR #363 removed the 'address indexed signer' parameter from the
BatchInfoAuthenticated event, but the Go derivation pipeline still used
the old 2-parameter signature. The keccak256 hash of the wrong signature
never matched actual log topics, causing CollectAuthenticatedBatches to
find zero events and reject every batch.

- Fix ABI string in batch_authenticator.go
- Update test mocks to use 2-topic logs matching the actual event
- Regenerate Go bindings from fresh forge artifacts

Co-authored-by: OpenCode <noreply@opencode.ai>
CollectAuthenticatedBatches walks backwards ~100 L1 blocks per call,
making an L1BlockRefByHash RPC call for each step. Since consecutive
L1 blocks share ~99 blocks in their lookback windows, these calls are
almost entirely redundant after the first traversal.

Add a second global LRU cache (blockRefCache) mapping block hash to
L1BlockRef, alongside the existing receipt cache. On steady state this
reduces L1BlockRefByHash RPC calls from ~100 per L1 block to ~0-1,
a ~50x reduction in total RPC overhead for the batch auth path.

Co-authored-by: OpenCode <noreply@opencode.ai>
…vePublishOnly

In-flight sendTxWithEspresso goroutines spawned before deactivation
can take ~25s to drain their queued Txmgr.Send calls. The previous
10s delay was insufficient, causing stale TEE batcher transactions to
appear in the post-switch monitoring window and failing the assertion.

Co-authored-by: OpenCode <noreply@opencode.ai>
Comment on lines +51 to +58
// resetBatchAuthCaches resets both global caches (receipt and block ref).
// This is only intended for use in tests to ensure isolation between test cases.
func resetBatchAuthCaches() {
batchAuthCache = nil
batchAuthCacheOnce = sync.Once{}
blockRefCache = nil
blockRefCacheOnce = sync.Once{}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why you went for a global variable, as opposed to just passing an instance down to where it is needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to reduce diff with upstream by keeping the changes in a separate file.

// Keyed by block hash so it is naturally reorg-safe: after a reorg the
// parent-hash traversal follows a different chain and stale entries are
// never hit. Thread-safe via lru.Cache's internal mutex.
batchAuthCache *lru.Cache[common.Hash, map[common.Hash]bool]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about not verifying the tee signature on chain and instead having the event emitted by the batch authenticator contract include the batch hash and TEE signature and then verifying those in the derivation pipeline?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be more efficient, but less generic and require an update to the derivation pipeline - and thus a hardfork - if we ever want to change how the verification is done, e.g, if we transition to ZK-proofs instead of TEE attestations.

@QuentinI
Copy link
Copy Markdown
Collaborator Author

@piersy @shenkeyao @jjeangal
Are you guys allright with the code as it stands now or do you have any further comments? Would like to push this over the line so that we can start preparing PRs targeting Celo's repository

Copy link
Copy Markdown
Member

@shenkeyao shenkeyao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Two devnet test CIs are failing--maybe rerun them?

@QuentinI
Copy link
Copy Markdown
Collaborator Author

Two devnet test CIs are failing

That's expected, needs this first EspressoSystems/kona-celo-fork#15

@QuentinI QuentinI force-pushed the ag/move-batch-auth-to-derivation-pipeline branch from 37b0281 to cff0be6 Compare March 20, 2026 10:35
@QuentinI QuentinI merged commit 0983f1c into celo-integration-rebase-14.2 Mar 20, 2026
51 of 53 checks passed
@QuentinI QuentinI deleted the ag/move-batch-auth-to-derivation-pipeline branch March 20, 2026 11:51
shenkeyao pushed a commit that referenced this pull request Mar 20, 2026
…357)

* Move batch authentication into derivation pipeline, remove BatchInbox contract

Move batch authentication from on-chain validation (BatchInbox contract calling
BatchAuthenticator.validateBatch) to off-chain verification in the derivation
pipeline via BatchInfoAuthenticated event scanning.

Key changes:
- Add batch_authenticator.go: event-based auth using CollectAuthenticatedBatches
  which scans a lookback window once per L1 block and returns authenticated hashes
- Add isBatchTxAuthorized helper shared by calldata and blob data sources
- Remove BatchInbox contract, interface, tests, bindings, and snapshots entirely
  (Espresso-introduced, never in Celo fork)
- Remove validateBatch/validateTeeBatch/validateNonTeeBatch from BatchAuthenticator
  (authenticateBatchInfo and signer management remain)
- Regenerate BatchAuthenticator Go bindings
- Revert BatchInbox-related fields from L1Deployments, ChainState, DeployEspressoOutput,
  and calculateBatchInboxAddr back to upstream signatures (introduce+remove no-ops)
- Update integration/devnet tests for EOA BatchInbox behavior

Co-authored-by: OpenCode <noreply@opencode.ai>

* Add FallbackBatcherAddress to derivation pipeline for non-TEE batcher auth

When batch authentication is enabled, the TEE batcher requires a matching
BatchInfoAuthenticated event on L1. The fallback (non-TEE) batcher does not
call authenticateBatchInfo, so its transactions had no auth events and were
rejected by the derivation pipeline.

Add FallbackBatcherAddress to rollup.Config and the derivation pipeline's
DataSourceConfig. When event-based auth finds no matching event for a batch,
it falls back to sender verification against the fallback batcher address.
This allows the non-TEE batcher to post batches without on-chain auth events
while still requiring event-based auth for the TEE batcher.

Co-authored-by: OpenCode <noreply@opencode.ai>

* Remove validBatchInfo mapping from BatchAuthenticator

The validBatchInfo mapping was written to on every authenticateBatchInfo
call but no longer read by the derivation pipeline, which now uses
BatchInfoAuthenticated events instead. Removing it saves ~20k gas per
call.

- Remove mapping declaration and write from BatchAuthenticator.sol
- Remove from IBatchAuthenticator interface
- Remove test assertions on validBatchInfo
- Update SECURITY_ANALYSIS.md to reflect event-based auth model
- Bump contract version to 1.1.0
- Regenerate ABI/storage snapshots and Go bindings

Co-authored-by: OpenCode <noreply@opencode.ai>

* Comments

* Fix txmgr wrapper

* Fix nonce collision: use separate account for fallback batcher

The fallback batcher (op-batcher-fallback) was using Anvil account #3
(0x90F79bf6EB2c4f870365E785982E1f101E93b906), which is also the Deployer
key used by devnet tests for admin transactions (SwitchBatcher,
TransferOwnership, etc.). When both the fallback batcher service and test
code sign L1 transactions from the same account concurrently, nonce
collisions cause transactions to never be mined, leading to timeouts.

Switch the fallback batcher to Anvil account #6
(0x976EA74026E726554dB657fA54763abd0C3a0aa9) across docker-compose,
prepare-allocs (nonTeeBatcher in rollup config), and .env.

Co-authored-by: OpenCode <noreply@opencode.ai>

* Add batcher-side idle check: inactive batcher skips publishing

Query BatchAuthenticator.activeIsTee() before publishing to L1.
The inactive batcher (TEE or fallback) now skips publishStateToL1
instead of posting transactions that would be ignored by the
derivation pipeline.

This was previously unnecessary because the BatchInbox contract's
validateBatch() would revert inactive batcher transactions during
eth_estimateGas. With the move to an EOA batch inbox, both batchers
can freely post, so an explicit idle check is needed.

Co-authored-by: OpenCode <noreply@opencode.ai>

* Fix event signature mismatch: BatchInfoAuthenticated(bytes32) not (bytes32,address)

PR #363 removed the 'address indexed signer' parameter from the
BatchInfoAuthenticated event, but the Go derivation pipeline still used
the old 2-parameter signature. The keccak256 hash of the wrong signature
never matched actual log topics, causing CollectAuthenticatedBatches to
find zero events and reject every batch.

- Fix ABI string in batch_authenticator.go
- Update test mocks to use 2-topic logs matching the actual event
- Regenerate Go bindings from fresh forge artifacts

Co-authored-by: OpenCode <noreply@opencode.ai>

* Cache L1BlockRef lookups in batch auth lookback window traversal

CollectAuthenticatedBatches walks backwards ~100 L1 blocks per call,
making an L1BlockRefByHash RPC call for each step. Since consecutive
L1 blocks share ~99 blocks in their lookback windows, these calls are
almost entirely redundant after the first traversal.

Add a second global LRU cache (blockRefCache) mapping block hash to
L1BlockRef, alongside the existing receipt cache. On steady state this
reduces L1BlockRefByHash RPC calls from ~100 per L1 block to ~0-1,
a ~50x reduction in total RPC overhead for the batch auth path.

Co-authored-by: OpenCode <noreply@opencode.ai>

* Increase batcher switch stabilization delay to 60s in TestBatcherActivePublishOnly

In-flight sendTxWithEspresso goroutines spawned before deactivation
can take ~25s to drain their queued Txmgr.Send calls. The previous
10s delay was insufficient, causing stale TEE batcher transactions to
appear in the post-switch monitoring window and failing the assertion.

Co-authored-by: OpenCode <noreply@opencode.ai>

* Reduce lookback window

* Pin eth2-val-tools

* Update succinct versions

---------

Co-authored-by: OpenCode <noreply@opencode.ai>
# Conflicts:
#	espresso/docker-compose.yml
#	op-batcher/batcher/driver.go
#	op-e2e/system/e2esys/setup.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants