Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
7dd0a79
contracts-bedrock: add Espresso BatchAuthenticator and supporting infra
QuentinI May 7, 2026
5e47299
Update packages/contracts-bedrock/src/L1/BatchAuthenticator.sol
QuentinI May 13, 2026
e88cbe8
Remove stray path
QuentinI May 13, 2026
2704403
Remove hardcoded Sepolia URL
QuentinI May 13, 2026
b99bcb9
Use OZ v5 in mock verifier
QuentinI May 13, 2026
dcff468
Fix Codex's suggestion
QuentinI May 20, 2026
c102ced
Lower pragma
QuentinI May 20, 2026
011c7a7
Remove unrelated foundry.toml changes
QuentinI May 20, 2026
d769ea2
scripts/checks: clean up exclude lists per PR review
QuentinI May 20, 2026
5651e2a
Fix re-initialization
QuentinI May 21, 2026
ab161a1
Add batcher address history
QuentinI May 25, 2026
400f1f5
contracts-bedrock: drop OZ TUP from espresso deploy
palango May 20, 2026
e898f5c
Remove pause from BatchAuthenticator
QuentinI May 25, 2026
0f4d001
Add a defensive check
QuentinI May 25, 2026
ea1f014
test: add end-to-end dual-batcher switch test
QuentinI May 25, 2026
c993c2a
Fix tests
QuentinI May 28, 2026
09533af
switchBatcher from toggle to a setter
QuentinI May 29, 2026
2d634fd
Use OZ Checkpoints for batcher history
QuentinI May 29, 2026
e6d65e0
contracts-bedrock: deploy espresso impls via vm.getCode, drop suppres…
QuentinI Jun 1, 2026
a99e175
Check batcher in Espresso mode
shenkeyao May 28, 2026
483e8e4
regenerate snapshots for UnauthorizedEspressoBatcher error
QuentinI Jun 2, 2026
2e5cc7d
contracts-bedrock: wire espresso proxies to shared OP Stack ProxyAdmin
QuentinI Jun 4, 2026
59f5d38
add caller to BatchInfoAuthenticated event
QuentinI Jun 4, 2026
5461fba
forge fmt
piersy Jun 9, 2026
96dfd24
Remove unused imports
piersy Jun 10, 2026
f132cbf
Rename tests to fit test name convention
piersy Jun 10, 2026
2354b89
op-chain-ops/script: resolve directory-qualified getCode artifact names
QuentinI Jun 11, 2026
70291e3
contracts-bedrock: fix semgrep checks-fast findings
QuentinI Jun 11, 2026
14210d4
contracts-bedrock: prevent same-block espressoBatcher history corruption
QuentinI Jun 15, 2026
68784e3
Fix formatting
piersy Jun 17, 2026
27f75a7
Fix recompile tests
QuentinI Jun 17, 2026
c87f184
op-node: add event-based batch authentication
QuentinI May 25, 2026
d797a1b
op-node: rename EspressoEnforcementTime to EspressoTime
QuentinI May 25, 2026
1f3150f
op-node: require BatchAuthenticatorAddress when Espresso is enabled
QuentinI Jun 2, 2026
9638afd
op-node: fix EspressoTime fork-activation doc comments
QuentinI Jun 2, 2026
dd772e3
op-node: bind authenticated batches to the authenticating caller
QuentinI Jun 8, 2026
7a4a401
op-node: replace global batch auth caches with dependency-injected in…
piersy Jun 2, 2026
9b14f40
Use rollup.Config in DataSourceConfig
piersy Jun 3, 2026
3a76b2d
op-node: fix up batch-auth cache DI and rollupCfg cherry-picks
QuentinI Jun 8, 2026
182d84e
Hardcode BatchAuthLookbackWindow
QuentinI Jun 8, 2026
5aff9d1
Tighten assertions for batch auth tests
piersy Jun 11, 2026
7979391
op-node: test Espresso fork boundary and per-commitment batch auth
piersy Jun 11, 2026
8917fef
op-node: isolate Espresso derivation tests into espresso_-prefixed files
piersy Jun 11, 2026
05cb149
Update op-node/rollup/derive/espresso_blob_data_source_test.go
piersy Jun 15, 2026
25b8fa9
Update op-node/rollup/derive/espresso_blob_data_source_test.go
piersy Jun 15, 2026
2782976
Expand LRU size comment
QuentinI Jun 18, 2026
93fa0dc
espresso/bindings: add regenerated BatchAuthenticator Go bindings
QuentinI May 27, 2026
1267e87
op-batcher: integrate fallback batcher authentication
QuentinI May 27, 2026
25a6c63
op-batcher: route fallback auth through ordered tx queue
QuentinI Jun 18, 2026
42382b6
Merge branch 'celo-rebase-18' into espresso/batcher-fallback
jcortejoso Jun 22, 2026
49c4321
Include espresso folder in docker build
jcortejoso Jun 22, 2026
76b729b
Merge branch 'celo-rebase-18' into espresso/batcher-fallback
lukeiannucci Jul 3, 2026
8632b97
address m1
lukeiannucci Jul 3, 2026
02831d8
m4
lukeiannucci Jul 3, 2026
9c9ba69
op-batcher: drop unused bind.ContractBackend from L1Client
palango Jun 24, 2026
4671fec
op-batcher: compute batch commitment via shared derive helpers
palango Jun 24, 2026
98dbe45
op-batcher: fix inaccurate fallback-auth comments
palango Jun 24, 2026
cbb6942
op-batcher: use sync.WaitGroup for fallback-auth watchers
palango Jun 24, 2026
4c437a2
op-batcher: extract newTxRef helper
palango Jun 24, 2026
25c5086
address comments
lukeiannucci Jul 6, 2026
f2ecb44
op-node+op-batcher: replace fallback-auth lead time with derivation-s…
lukeiannucci Jul 13, 2026
ae8067d
op-batcher: fix fallback-auth nonce-gap hazard (m3) (#467)
lukeiannucci Jul 20, 2026
229eea2
op-service/txmgr: correct SendPairAsync nonce-reset doc
piersy Jul 20, 2026
ebce71e
rename file, cleanup and address comments
lukeiannucci Jul 20, 2026
b0bc51b
add unit tests for fallback auth
lukeiannucci Jul 20, 2026
8957155
remove unecessary metrics
lukeiannucci Jul 20, 2026
90b3ed5
op-service/txmgr: inline leg and pairSend types
piersy Jul 21, 2026
1cc52fc
Rename pair.go espresso_txmgr.go
piersy Jul 21, 2026
1da0b58
op-service/txmgr: rename pair_test.go to espresso_txmgr_test.go
piersy Jul 21, 2026
192d873
op-service/txmgr: document repair reasoning on sendPair
piersy Jul 21, 2026
cfcd9ac
op-batcher: gate fallback-auth confirmations check on EspressoTime
piersy Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,277 changes: 2,277 additions & 0 deletions espresso/bindings/batch_authenticator.go

Large diffs are not rendered by default.

39 changes: 38 additions & 1 deletion op-batcher/batcher/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ type txRef struct {
size int
}

// newTxRef builds the txRef that identifies a batch submission across the txmgr
// queue and receipt handling.
func newTxRef(txdata txData, isCancel bool) txRef {
return txRef{
id: txdata.ID(),
isCancel: isCancel,
isBlob: txdata.daType == DaTypeBlob,
daType: txdata.daType,
size: txdata.Len(),
}
}

func (r txRef) String() string {
return r.string(func(id txID) string { return id.String() })
}
Expand Down Expand Up @@ -124,6 +136,15 @@ type BatchSubmitter struct {
throttleController *throttler.ThrottleController

publishSignal chan pubInfo

// authGroup tracks the fallback batcher's receipt-watcher goroutines (one
// per auth+batch pair) so the publishing loop can drain them via
// waitForAuthGroup before closing receiptsCh. New watchers are back-pressured
// (not hard-bounded) by the txmgr Queue: queue.Send blocks at
// MaxPendingTransactions, so watchers are created no faster than txs drain,
// though a slow receipts loop can briefly leave more than that parked on their
// final receiptsCh send.
authGroup sync.WaitGroup
}

// NewBatchSubmitter initializes the BatchSubmitter driver from a preconfigured DriverSetup
Expand Down Expand Up @@ -516,6 +537,12 @@ func (l *BatchSubmitter) publishingLoop(ctx context.Context, wg *sync.WaitGroup,
}
}

// Wait for all in-flight fallback-auth submissions to complete to prevent
// new transactions being queued. No-op when the rollup is not configured
// with a BatchAuthenticator or when the EspressoTime hardfork has not
// activated.
l.waitForAuthGroup()

// We _must_ wait for all senders on receiptsCh to finish before we can close it.
if err := txQueue.Wait(); err != nil {
if !errors.Is(err, context.Canceled) {
Expand Down Expand Up @@ -1022,6 +1049,9 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef

type TxSender[T any] interface {
Send(id T, candidate txmgr.TxCandidate, receiptCh chan txmgr.TxReceipt[T])
// SendPair submits an ordered pair of transactions holding a single
// max-pending slot; see txmgr.Queue.SendPair.
SendPair(firstID T, first txmgr.TxCandidate, firstCh chan txmgr.TxReceipt[T], secondID T, second txmgr.TxCandidate, secondCh chan txmgr.TxReceipt[T])
}

// sendTx uses the txmgr queue to send the given transaction candidate after setting its
Expand All @@ -1035,7 +1065,14 @@ func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.T
candidate.GasLimit = floorDataGas
}

queue.Send(txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.daType == DaTypeBlob, daType: txdata.daType, size: txdata.Len()}, *candidate, receiptsCh)
// Route through the fallback-auth path when a BatchAuthenticator is
// configured and the EspressoTime hardfork is active. Falls through to
// the upstream queue.Send path otherwise.
if l.dispatchAuthenticatedSendTx(txdata, isCancel, candidate, queue, receiptsCh) {
return
}

queue.Send(newTxRef(txdata, isCancel), *candidate, receiptsCh)
}

func (l *BatchSubmitter) blobTxCandidate(data txData) (*txmgr.TxCandidate, error) {
Expand Down
8 changes: 6 additions & 2 deletions op-batcher/batcher/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ func (q *MockTxQueue) Send(ref txRef, candidate txmgr.TxCandidate, receiptCh cha
q.m.Store(ref.id.String(), candidate)
}

func (q *MockTxQueue) SendPair(firstRef txRef, first txmgr.TxCandidate, firstCh chan txmgr.TxReceipt[txRef], secondRef txRef, second txmgr.TxCandidate, secondCh chan txmgr.TxReceipt[txRef]) {
q.m.Store(firstRef.id.String(), first)
q.m.Store(secondRef.id.String(), second)
}

func (q *MockTxQueue) Load(id string) txmgr.TxCandidate {
c, _ := q.m.Load(id)
return c.(txmgr.TxCandidate)
Expand Down Expand Up @@ -480,8 +485,7 @@ func TestBatchSubmitter_CriticalError(t *testing.T) {
// ======= ALTDA TESTS =======

// fakeL1Client is just a dummy struct. All fault injection is done via the fakeTxMgr (which doesn't interact with this fakeL1Client).
type fakeL1Client struct {
}
type fakeL1Client struct{}

func (f *fakeL1Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
if number == nil {
Expand Down
69 changes: 69 additions & 0 deletions op-batcher/batcher/espresso_fallback_gate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package batcher

import (
"context"
"fmt"

"github.com/ethereum/go-ethereum/common"

"github.com/ethereum-optimism/optimism/op-service/txmgr"
)

// dispatchAuthenticatedSendTx routes sendTx through the fallback-batcher
// post-fork auth path, returning true when the tx has been handed off to
// authGroup. Returns false to mean "fall through to the upstream queue.Send
// path" — pre-fork operation and any cancel tx.
//
// The fallback batcher consults isFallbackAuthRequired to gate authentication
// behind the EspressoTime hardfork: pre-fork the verifier accepts plain
// sender-authenticated batches, and the BatchAuthenticator contract is
// irrelevant.
func (l *BatchSubmitter) dispatchAuthenticatedSendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) bool {
if isCancel {
return false
}
fallbackAuthRequired, err := l.isFallbackAuthRequired(l.killCtx)
if err != nil {
receiptsCh <- txmgr.TxReceipt[txRef]{
ID: newTxRef(txdata, isCancel),
Err: fmt.Errorf("failed to evaluate fallback-auth gate: %w", err),
}
return true
}
if !fallbackAuthRequired {
return false
}
l.sendTxWithFallbackAuth(txdata, isCancel, candidate, queue, receiptsCh)
return true
}

// isFallbackAuthRequired reports whether the fallback (non-TEE) batcher must
// route its batch txs through BatchAuthenticator.authenticateBatchInfo before
// posting to the BatchInbox. It returns false if the rollup config has a
// zero BatchAuthenticatorAddress, indicating that the BatchAuthenticator-based
// authentication path is not in use.
//
// The gate switches at plain Espresso activation (tip.Time >= EspressoTime), a full grace
// period before the verifier enforces event-based authentication. See
// derive.BatchAuthEnforcementDelaySecs for the full grace-period mechanism and why the
// batcher and verifier gates are offset.
func (l *BatchSubmitter) isFallbackAuthRequired(ctx context.Context) (bool, error) {
if l.RollupConfig.BatchAuthenticatorAddress == (common.Address{}) {
return false, nil
}
tip, err := l.l1Tip(ctx)
if err != nil {
return false, fmt.Errorf("failed to fetch L1 tip for fallback-auth gate: %w", err)
}
return l.RollupConfig.IsEspresso(tip.Time), nil
}

// waitForAuthGroup blocks until all in-flight fallback-auth watcher goroutines
// have finished. publishingLoop calls it before closing receiptsCh: each watcher
// is a sender on receiptsCh, so the receipts loop must still be draining
// receiptsCh at this point or a watcher's final send would block forever. Each
// watcher always terminates because the txmgr Queue emits exactly one receipt per
// Send, even on context cancellation.
func (l *BatchSubmitter) waitForAuthGroup() {
l.authGroup.Wait()
}
106 changes: 106 additions & 0 deletions op-batcher/batcher/espresso_fallback_gate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package batcher

import (
"context"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)

type mockFixedTimeL1Client struct {
time uint64
}

func (f *mockFixedTimeL1Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return &types.Header{Number: big.NewInt(0), Time: f.time}, nil
}

func (f *mockFixedTimeL1Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
return 0, nil
}

func u64(v uint64) *uint64 { return &v }

type fallbackAuthGateCase struct {
name string
authAddr common.Address
espressoTime *uint64
tipTime uint64
want bool
}

// TestIsFallbackAuthRequired_ForkBoundary locks the batcher's own side of the
// Espresso fork boundary: the fallback batcher must begin authenticating exactly
// at EspressoTime activation and never before, and only when a BatchAuthenticator
// is configured. This is the counterpart to the derivation-side
// TestDataAndHashesFromTxsForkBoundary — a silent divergence here would strand
// batches (auth expected by the verifier but never posted, or posted needlessly).
func TestIsFallbackAuthRequired_ForkBoundary(t *testing.T) {
const espressoTime = 1000
authAddr := common.HexToAddress("0x00000000000000000000000000000000000000aa")

tests := []fallbackAuthGateCase{
{
name: "no authenticator configured is never gated",
authAddr: common.Address{},
espressoTime: u64(espressoTime),
tipTime: espressoTime, // even at/after activation
want: false,
},
{
name: "espresso not scheduled is never gated",
authAddr: authAddr,
espressoTime: nil,
tipTime: espressoTime,
want: false,
},
{
name: "one second before activation is not gated",
authAddr: authAddr,
espressoTime: u64(espressoTime),
tipTime: espressoTime - 1,
want: false,
},
{
name: "exactly at activation is gated",
authAddr: authAddr,
espressoTime: u64(espressoTime),
tipTime: espressoTime,
want: true,
},
{
name: "after activation is gated",
authAddr: authAddr,
espressoTime: u64(espressoTime),
tipTime: espressoTime + 1,
want: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
l := &BatchSubmitter{}
l.Log = testlog.Logger(t, log.LevelDebug)
l.Metr = metrics.NoopMetrics
l.RollupConfig = &rollup.Config{
BatchAuthenticatorAddress: test.authAddr,
EspressoTime: test.espressoTime,
}
l.Config.NetworkTimeout = time.Second
l.L1Client = &mockFixedTimeL1Client{time: test.tipTime}

got, err := l.isFallbackAuthRequired(context.Background())
require.NoError(t, err)
require.Equal(t, test.want, got)
})
}
}
Loading
Loading