From f8881f7055030ebe1fece72e724ad7e594ae2ba3 Mon Sep 17 00:00:00 2001 From: Piers Powlesland Date: Thu, 2 Jul 2026 12:10:11 +0100 Subject: [PATCH 1/3] refactor(derive): drop dead Espresso calldata authentication path The calldata data source is only reached pre-ecotone: OpenData routes to the blob source whenever ecotone is active (ref.Time >= ecotone_time). Every Celo chain sets ecotone_time = 0, and Espresso (espresso_time) activates strictly later, so the Espresso event-based batch-authentication branch that #457 folded into DataFromEVMTransactions could never fire on a real chain. Restore the calldata data source to its pre-Espresso form. calldata_source.go is byte-identical to before #457; #457 had split isValidBatchTx into a type/To-only check plus isAuthorizedBatchSender to serve the blob source's event-based path, which rippled a needless change into the calldata source. Reinstate the original 5-arg isValidBatchTx (composed from isBatchTxToInbox + isAuthorizedBatchSender) and point the blob source at isBatchTxToInbox. calldata_source_test.go is also restored except for one line: DataSourceConfig gained fields (rollupCfg, batchAuthCaches), so its literal must use named fields instead of positional. The blob source keeps Espresso event-based authentication and also handles plain calldata-carrying batcher txs post-ecotone, so no behaviour is lost. Delete the now-dead Espresso calldata test and move its shared mockAuthEvents helper into the blob data source test, its only user. Mirrors the celo-kona removal (drop CeloCalldataSource, use upstream CalldataSource). --- op-node/rollup/derive/blob_data_source.go | 2 +- op-node/rollup/derive/calldata_source.go | 79 +--- op-node/rollup/derive/calldata_source_test.go | 17 +- op-node/rollup/derive/data_source.go | 19 +- .../derive/espresso_blob_data_source_test.go | 77 +++ .../derive/espresso_calldata_source_test.go | 447 ------------------ 6 files changed, 117 insertions(+), 524 deletions(-) delete mode 100644 op-node/rollup/derive/espresso_calldata_source_test.go diff --git a/op-node/rollup/derive/blob_data_source.go b/op-node/rollup/derive/blob_data_source.go index 539b432f948..c0bfa701135 100644 --- a/op-node/rollup/derive/blob_data_source.go +++ b/op-node/rollup/derive/blob_data_source.go @@ -147,7 +147,7 @@ func dataAndHashesFromTxs(ctx context.Context, txs types.Transactions, config *D var hashes []common.Hash for _, tx := range txs { // skip any non-batcher transactions (wrong type or wrong To address) - if !isValidBatchTx(tx, config.batchInboxAddress, logger) { + if !isBatchTxToInbox(tx, config.batchInboxAddress) { continue } diff --git a/op-node/rollup/derive/calldata_source.go b/op-node/rollup/derive/calldata_source.go index 2803301378e..0e8147261e9 100644 --- a/op-node/rollup/derive/calldata_source.go +++ b/op-node/rollup/derive/calldata_source.go @@ -24,7 +24,7 @@ type CalldataSource struct { // Required to re-attempt fetching ref eth.L1BlockRef dsCfg DataSourceConfig - fetcher L1Fetcher + fetcher L1TransactionFetcher log log.Logger batcherAddr common.Address @@ -32,27 +32,21 @@ type CalldataSource struct { // NewCalldataSource creates a new calldata source. It suppresses errors in fetching the L1 block if they occur. // If there is an error, it will attempt to fetch the result on the next call to `Next`. -func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1Fetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter { - closedSource := &CalldataSource{ - open: false, - ref: ref, - dsCfg: dsCfg, - fetcher: fetcher, - log: log, - batcherAddr: batcherAddr, - } - +func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1TransactionFetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter { _, txs, err := fetcher.InfoAndTxsByHash(ctx, ref.Hash) if err != nil { - return closedSource - } - data, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, txs, fetcher, ref, log.New("origin", ref)) - if err != nil { - return closedSource + return &CalldataSource{ + open: false, + ref: ref, + dsCfg: dsCfg, + fetcher: fetcher, + log: log, + batcherAddr: batcherAddr, + } } return &CalldataSource{ open: true, - data: data, + data: DataFromEVMTransactions(dsCfg, batcherAddr, txs, log.New("origin", ref)), } } @@ -61,17 +55,14 @@ func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConf // otherwise it returns a temporary error if fetching the block returns an error. func (ds *CalldataSource) Next(ctx context.Context) (eth.Data, error) { if !ds.open { - _, txs, err := ds.fetcher.InfoAndTxsByHash(ctx, ds.ref.Hash) - if errors.Is(err, ethereum.NotFound) { + if _, txs, err := ds.fetcher.InfoAndTxsByHash(ctx, ds.ref.Hash); err == nil { + ds.open = true + ds.data = DataFromEVMTransactions(ds.dsCfg, ds.batcherAddr, txs, ds.log) + } else if errors.Is(err, ethereum.NotFound) { return nil, NewResetError(fmt.Errorf("failed to open calldata source: %w", err)) - } else if err != nil { + } else { return nil, NewTemporaryError(fmt.Errorf("failed to open calldata source: %w", err)) } - ds.data, err = DataFromEVMTransactions(ctx, ds.dsCfg, ds.batcherAddr, txs, ds.fetcher, ds.ref, ds.log) - if err != nil { - return nil, err - } - ds.open = true } if len(ds.data) == 0 { return nil, io.EOF @@ -85,42 +76,12 @@ func (ds *CalldataSource) Next(ctx context.Context) (eth.Data, error) { // DataFromEVMTransactions filters all of the transactions and returns the calldata from transactions // that are sent to the batch inbox address from the batch sender address. // This will return an empty array if no valid transactions are found. -// -// Pre-Espresso (the L1 origin time of `ref` is < *EspressoTime, or unset), -// this runs upstream Optimism semantics: filter by batch inbox + sender == -// batcher. -// -// Post-Espresso, it collects all authenticated batch hashes from a lookback -// window once and rejects any batch whose commitment hash is not in the -// authenticated set. -func DataFromEVMTransactions(ctx context.Context, dsCfg DataSourceConfig, batcherAddr common.Address, txs types.Transactions, fetcher L1Fetcher, ref eth.L1BlockRef, log log.Logger) ([]eth.Data, error) { - // Only collect authenticated batch commitments when the Espresso fork is - // active at the L1 origin time of the block we're scanning. Pre-fork, the - // upstream sender-based authorization path inside isBatchTxAuthorized is used - // and the authenticatedHashes map is unused. - var authenticatedHashes map[common.Hash]common.Address - if dsCfg.rollupCfg.IsEspresso(ref.Time) { - var err error - authenticatedHashes, err = CollectAuthenticatedBatches( - ctx, fetcher, ref, dsCfg.rollupCfg.BatchAuthenticatorAddress, dsCfg.batchAuthCaches, log, - ) - if err != nil { - return nil, err - } - } - +func DataFromEVMTransactions(dsCfg DataSourceConfig, batcherAddr common.Address, txs types.Transactions, log log.Logger) []eth.Data { out := []eth.Data{} for _, tx := range txs { - if !isValidBatchTx(tx, dsCfg.batchInboxAddress, log) { - continue + if isValidBatchTx(tx, dsCfg.l1Signer, dsCfg.batchInboxAddress, batcherAddr, log) { + out = append(out, tx.Data()) } - - batchHash := ComputeCalldataBatchHash(tx.Data()) - if !isBatchTxAuthorized(tx, dsCfg, batcherAddr, batchHash, authenticatedHashes, ref.Time, log) { - continue - } - - out = append(out, tx.Data()) } - return out, nil + return out } diff --git a/op-node/rollup/derive/calldata_source_test.go b/op-node/rollup/derive/calldata_source_test.go index 4d6a854155e..6b71be3ed69 100644 --- a/op-node/rollup/derive/calldata_source_test.go +++ b/op-node/rollup/derive/calldata_source_test.go @@ -1,7 +1,6 @@ package derive import ( - "context" "crypto/ecdsa" "math/big" "math/rand" @@ -116,24 +115,14 @@ func TestDataFromEVMTransactions(t *testing.T) { var expectedData []eth.Data var txs []*types.Transaction for i, tx := range tc.txs { - transaction := tx.Create(t, signer, rng) - txs = append(txs, transaction) - + txs = append(txs, tx.Create(t, signer, rng)) if tx.good { expectedData = append(expectedData, txs[i].Data()) } } - // Legacy mode (no batch authenticator, Espresso inactive) — uses sender-based auth - dsCfg := DataSourceConfig{ - l1Signer: cfg.L1Signer(), - batchInboxAddress: cfg.BatchInboxAddress, - rollupCfg: cfg, - } - ref := eth.L1BlockRef{Number: 1} - // In legacy mode, no L1Fetcher calls are needed for auth (sender check is local) - out, err := DataFromEVMTransactions(context.Background(), dsCfg, batcherAddr, txs, nil, ref, testlog.Logger(t, log.LevelCrit)) - require.NoError(t, err) + out := DataFromEVMTransactions(DataSourceConfig{l1Signer: cfg.L1Signer(), batchInboxAddress: cfg.BatchInboxAddress}, batcherAddr, txs, testlog.Logger(t, log.LevelCrit)) require.ElementsMatch(t, expectedData, out) } + } diff --git a/op-node/rollup/derive/data_source.go b/op-node/rollup/derive/data_source.go index 3d659250379..74b50335b2f 100644 --- a/op-node/rollup/derive/data_source.go +++ b/op-node/rollup/derive/data_source.go @@ -110,13 +110,26 @@ type DataSourceConfig struct { batchAuthCaches *BatchAuthCaches } -// isValidBatchTx checks basic transaction validity for batch submission: +// isValidBatchTx returns true if: +// 1. the transaction type is any of Legacy, ACL, DynamicFee, Blob, or Deposit (for L3s). +// 2. the transaction has a To() address that matches the batch inbox address, and +// 3. the transaction has a valid signature from the batcher address +// +// This is the pre-Espresso (upstream) sender-based validity check used by the calldata data +// source. The blob data source instead composes isBatchTxToInbox with isBatchTxAuthorized so it +// can apply event-based authorization post-Espresso, where the submitter need not be the +// configured batcher address. +func isValidBatchTx(tx *types.Transaction, l1Signer types.Signer, batchInboxAddr, batcherAddr common.Address, logger log.Logger) bool { + return isBatchTxToInbox(tx, batchInboxAddr) && isAuthorizedBatchSender(tx, l1Signer, batcherAddr, logger) +} + +// isBatchTxToInbox checks basic transaction validity for batch submission: // 1. the transaction type is any of Legacy, ACL, DynamicFee, Blob, or Deposit (for L3s). // 2. the transaction has a To() address that matches the batch inbox address // // It does NOT check authentication (sender or event-based) — that is handled separately -// by isBatchTxAuthorized. -func isValidBatchTx(tx *types.Transaction, batchInboxAddr common.Address, logger log.Logger) bool { +// by isAuthorizedBatchSender / isBatchTxAuthorized. +func isBatchTxToInbox(tx *types.Transaction, batchInboxAddr common.Address) bool { // For now, we want to disallow the SetCodeTx type or any future types. if tx.Type() > types.BlobTxType && tx.Type() != types.DepositTxType { return false diff --git a/op-node/rollup/derive/espresso_blob_data_source_test.go b/op-node/rollup/derive/espresso_blob_data_source_test.go index fb96ed62005..e0923057a84 100644 --- a/op-node/rollup/derive/espresso_blob_data_source_test.go +++ b/op-node/rollup/derive/espresso_blob_data_source_test.go @@ -21,6 +21,83 @@ import ( "github.com/ethereum/go-ethereum/log" ) +// mockAuthEvents sets up L1 mock expectations for CollectAuthenticatedBatches to find auth events +// for the given batch hashes at the given ref's block number. Auth events for batch hashes in +// `authenticated` are placed in the ref block's receipts; all other blocks in the lookback +// window have empty receipts. +// +// CollectAuthenticatedBatches traverses backward from ref via parent hashes, so this helper +// builds a chain of L1BlockRef values with proper parent-hash linkage, sets up FetchReceipts +// for each block, and L1BlockRefByHash for each parent. +// +// The auth events are emitted with `caller` as the indexed caller, which the +// pipeline matches against the batch transaction's L1 sender. Tests pass the +// expected batcher address here. +// +// Returns the updated ref with its ParentHash properly set to the chain. Callers must use +// the returned ref when calling functions that invoke CollectAuthenticatedBatches. +func mockAuthEvents(l1F *testutils.MockL1Source, rng *rand.Rand, ref eth.L1BlockRef, authenticatorAddr, caller common.Address, authenticated []common.Hash) eth.L1BlockRef { + startBlock := ref.Number + if startBlock > BatchAuthLookbackWindow { + startBlock = ref.Number - BatchAuthLookbackWindow + } else { + startBlock = 0 + } + windowSize := ref.Number - startBlock + 1 + + // Build the auth receipts for the ref block. The commitment is the unindexed + // data argument; only the caller is indexed (Topics[1]). + var authLogs []*types.Log + for _, bh := range authenticated { + authLogs = append(authLogs, &types.Log{ + Address: authenticatorAddr, + Topics: []common.Hash{ + BatchInfoAuthenticatedABIHash, + common.BytesToHash(caller.Bytes()), + }, + Data: bh.Bytes(), + }) + } + authReceipts := types.Receipts{} + if len(authLogs) > 0 { + authReceipts = types.Receipts{{Status: types.ReceiptStatusSuccessful, Logs: authLogs}} + } + + // Build parent-hash-linked chain from startBlock to ref.Number. + // chain[i] corresponds to block number startBlock + i. + chain := make([]eth.L1BlockRef, windowSize) + for i := uint64(0); i < windowSize; i++ { + blockNum := startBlock + i + if blockNum == ref.Number { + chain[i] = ref + } else { + chain[i] = eth.L1BlockRef{Number: blockNum, Hash: testutils.RandomHash(rng)} + } + if i > 0 { + chain[i].ParentHash = chain[i-1].Hash + } + } + + // Update the ref at the end of the chain with the correct ParentHash + updatedRef := chain[windowSize-1] + + // Set up expectations for backward traversal: ref -> ref-1 -> ... -> startBlock + for i := int(windowSize) - 1; i >= 0; i-- { + blockRef := chain[i] + if blockRef.Number == ref.Number { + l1F.ExpectFetchReceipts(blockRef.Hash, nil, authReceipts, nil) + } else { + l1F.ExpectFetchReceipts(blockRef.Hash, nil, types.Receipts{}, nil) + } + // L1BlockRefByHash is called for every parent except when we've reached the end of the window + if i > 0 { + l1F.ExpectL1BlockRefByHash(chain[i-1].Hash, chain[i-1], nil) + } + } + + return updatedRef +} + // TestDataAndHashesFromTxsEventAuth tests event-based batch authentication for both // calldata and blob transactions in the blob data source path. // diff --git a/op-node/rollup/derive/espresso_calldata_source_test.go b/op-node/rollup/derive/espresso_calldata_source_test.go deleted file mode 100644 index b1d314d3aae..00000000000 --- a/op-node/rollup/derive/espresso_calldata_source_test.go +++ /dev/null @@ -1,447 +0,0 @@ -package derive - -import ( - "context" - "crypto/ecdsa" - "math/big" - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-service/testutils" -) - -// mockAuthEvents sets up L1 mock expectations for CollectAuthenticatedBatches to find auth events -// for the given batch hashes at the given ref's block number. Auth events for batch hashes in -// `authenticated` are placed in the ref block's receipts; all other blocks in the lookback -// window have empty receipts. -// -// CollectAuthenticatedBatches traverses backward from ref via parent hashes, so this helper -// builds a chain of L1BlockRef values with proper parent-hash linkage, sets up FetchReceipts -// for each block, and L1BlockRefByHash for each parent. -// -// The auth events are emitted with `caller` as the indexed caller, which the -// pipeline matches against the batch transaction's L1 sender. Tests pass the -// expected batcher address here. -// -// Returns the updated ref with its ParentHash properly set to the chain. Callers must use -// the returned ref when calling functions that invoke CollectAuthenticatedBatches. -func mockAuthEvents(l1F *testutils.MockL1Source, rng *rand.Rand, ref eth.L1BlockRef, authenticatorAddr, caller common.Address, authenticated []common.Hash) eth.L1BlockRef { - startBlock := ref.Number - if startBlock > BatchAuthLookbackWindow { - startBlock = ref.Number - BatchAuthLookbackWindow - } else { - startBlock = 0 - } - windowSize := ref.Number - startBlock + 1 - - // Build the auth receipts for the ref block. The commitment is the unindexed - // data argument; only the caller is indexed (Topics[1]). - var authLogs []*types.Log - for _, bh := range authenticated { - authLogs = append(authLogs, &types.Log{ - Address: authenticatorAddr, - Topics: []common.Hash{ - BatchInfoAuthenticatedABIHash, - common.BytesToHash(caller.Bytes()), - }, - Data: bh.Bytes(), - }) - } - authReceipts := types.Receipts{} - if len(authLogs) > 0 { - authReceipts = types.Receipts{{Status: types.ReceiptStatusSuccessful, Logs: authLogs}} - } - - // Build parent-hash-linked chain from startBlock to ref.Number. - // chain[i] corresponds to block number startBlock + i. - chain := make([]eth.L1BlockRef, windowSize) - for i := uint64(0); i < windowSize; i++ { - blockNum := startBlock + i - if blockNum == ref.Number { - chain[i] = ref - } else { - chain[i] = eth.L1BlockRef{Number: blockNum, Hash: testutils.RandomHash(rng)} - } - if i > 0 { - chain[i].ParentHash = chain[i-1].Hash - } - } - - // Update the ref at the end of the chain with the correct ParentHash - updatedRef := chain[windowSize-1] - - // Set up expectations for backward traversal: ref -> ref-1 -> ... -> startBlock - for i := int(windowSize) - 1; i >= 0; i-- { - blockRef := chain[i] - if blockRef.Number == ref.Number { - l1F.ExpectFetchReceipts(blockRef.Hash, nil, authReceipts, nil) - } else { - l1F.ExpectFetchReceipts(blockRef.Hash, nil, types.Receipts{}, nil) - } - // L1BlockRefByHash is called for every parent except when we've reached the end of the window - if i > 0 { - l1F.ExpectL1BlockRefByHash(chain[i-1].Hash, chain[i-1], nil) - } - } - - return updatedRef -} - -// TestDataFromEVMTransactionsEventAuth tests event-based batch authentication -// where a BatchInfoAuthenticated event in the lookback window authorizes a batch. -// -// Event-based authentication is only active post-Espresso; the fixture -// activates the fork at L1 origin time 0 (genesis) so all test refs satisfy -// ref.Time >= *EspressoTime. -func TestDataFromEVMTransactionsEventAuth(t *testing.T) { - rng := rand.New(rand.NewSource(42)) - batcherPriv := testutils.RandomKey() - altAuthor := testutils.RandomKey() - batchInboxAddr := testutils.RandomAddress(rng) - authenticatorAddr := testutils.RandomAddress(rng) - batcherAddr := crypto.PubkeyToAddress(batcherPriv.PublicKey) - altAuthorAddr := crypto.PubkeyToAddress(altAuthor.PublicKey) - signer := types.NewCancunSigner(big.NewInt(100)) - - espressoTime := uint64(0) - dsCfg := DataSourceConfig{ - l1Signer: signer, - batchInboxAddress: batchInboxAddr, - rollupCfg: &rollup.Config{ - EspressoTime: &espressoTime, - BatchAuthenticatorAddress: authenticatorAddr, - }, - batchAuthCaches: NewBatchAuthCaches(), - } - - ctx := context.Background() - logger := testlog.Logger(t, log.LevelDebug) - - t.Run("authenticated tx accepted", func(t *testing.T) { - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData, - }) - require.NoError(t, err) - - // Use block number 1 so lookback window is [0, 1] — only 2 blocks to mock - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - batchHash := ComputeCalldataBatchHash(txData) - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, []common.Hash{batchHash}) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 1) - require.Equal(t, eth.Data(txData), out[0]) - l1F.AssertExpectations(t) - }) - - t.Run("unauthenticated tx from unknown sender rejected", func(t *testing.T) { - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx, err := types.SignNewTx(altAuthor, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - // No auth events — empty authenticated list - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, nil) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 0) - l1F.AssertExpectations(t) - }) - - t.Run("fallback batcher without auth event rejected", func(t *testing.T) { - // The fallback batcher now also authenticates via BatchAuthenticator events. - // Without an auth event, even the SystemConfig batcher address is rejected. - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, nil) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 0, "fallback batcher without auth event should be rejected") - l1F.AssertExpectations(t) - }) - - t.Run("wrong inbox address rejected without auth check", func(t *testing.T) { - // Tx to wrong address should be filtered by isValidBatchTx. - // CollectAuthenticatedBatches still runs (it's a block-level operation), - // but no tx passes the inbox address check. - l1F := &testutils.MockL1Source{} - wrongAddr := testutils.RandomAddress(rng) - txData := testutils.RandomData(rng, 100) - tx, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &wrongAddr, Data: txData, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - // Mock the lookback window scan (returns no authenticated hashes) - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, nil) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 0) - l1F.AssertExpectations(t) - }) - - t.Run("mixed: only event-authenticated txs accepted", func(t *testing.T) { - l1F := &testutils.MockL1Source{} - // tx1: has auth event — should be accepted - txData1 := testutils.RandomData(rng, 100) - tx1, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData1, - }) - require.NoError(t, err) - - // tx2: no auth event — should be rejected even though sender is batcherAddr - txData2 := testutils.RandomData(rng, 100) - tx2, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 1, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData2, - }) - require.NoError(t, err) - - // tx3: unknown sender without auth event — should be rejected - txData3 := testutils.RandomData(rng, 100) - tx3, err := types.SignNewTx(altAuthor, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 2, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData3, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - batchHash1 := ComputeCalldataBatchHash(txData1) - // Only tx1 has an auth event (caller = batcherAddr, matching tx1's sender). - // tx2 and tx3 do not — both should be rejected. - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, []common.Hash{batchHash1}) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx1, tx2, tx3}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 1, "only event-authenticated tx should pass") - require.Equal(t, eth.Data(txData1), out[0]) - l1F.AssertExpectations(t) - }) - - t.Run("auth event accepts a non-batcher sender that matches its caller", func(t *testing.T) { - // Event-based mode does not require the SystemConfig batcher: any sender is - // accepted as long as it matches the caller that emitted the auth event. - // Here altAuthor both submits the batch and is the auth event caller. - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx, err := types.SignNewTx(altAuthor, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - batchHash := ComputeCalldataBatchHash(txData) - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, altAuthorAddr, []common.Hash{batchHash}) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 1) - require.Equal(t, eth.Data(txData), out[0]) - l1F.AssertExpectations(t) - }) - - t.Run("authenticated batch from a different sender than the caller is rejected", func(t *testing.T) { - // The batch commitment is authenticated, but by batcherAddr; the batch tx is - // submitted by altAuthor. The sender must match the auth event caller, so the - // batch is rejected even though the commitment was authenticated. - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx, err := types.SignNewTx(altAuthor, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txData, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - batchHash := ComputeCalldataBatchHash(txData) - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, []common.Hash{batchHash}) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 0, "batch authenticated by a different address than the submitter must be rejected") - l1F.AssertExpectations(t) - }) - - t.Run("multiple authenticated txs each accepted for their own commitment", func(t *testing.T) { - // Two distinct batches, each authenticated by its own commitment event from the - // batcher. Both must be accepted, in order, each mapped to its own data — verifying - // every tx is matched against its own commitment, not just "some" authenticated entry. - l1F := &testutils.MockL1Source{} - txDataA := testutils.RandomData(rng, 100) - txA, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txDataA, - }) - require.NoError(t, err) - txDataB := testutils.RandomData(rng, 100) - txB, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 1, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: txDataB, - }) - require.NoError(t, err) - - ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, - []common.Hash{ComputeCalldataBatchHash(txDataA), ComputeCalldataBatchHash(txDataB)}) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{txA, txB}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 2) - require.Equal(t, eth.Data(txDataA), out[0], "first tx must map to its own data") - require.Equal(t, eth.Data(txDataB), out[1], "second tx must map to its own data") - l1F.AssertExpectations(t) - }) -} - -// TestDataFromEVMTransactionsForkBoundary exercises the Espresso fork gate flipping -// across a single fixed DataSourceConfig. Pre-Espresso (L1 origin time < EspressoTime) -// must use upstream sender-based authorization with no event scanning at all; at and -// after activation (L1 origin time >= EspressoTime) it must switch to event-based -// authentication. -// -// This pins the gate boundary — the IsEspresso(ref.Time) check (`timestamp >= -// *EspressoTime`) consulted in DataFromEVMTransactions and isBatchTxAuthorized. The same -// batcher transaction is accepted pre-fork without any auth event, but rejected at the -// activation block unless a BatchInfoAuthenticated event authorizes it. A regression in -// the boundary is caught in both directions: a pre-fork block would start scanning -// receipts (unexpected mock calls panic), and the activation block would otherwise accept -// an unauthenticated batch. -func TestDataFromEVMTransactionsForkBoundary(t *testing.T) { - rng := rand.New(rand.NewSource(99)) - batcherPriv := testutils.RandomKey() - altAuthor := testutils.RandomKey() - batchInboxAddr := testutils.RandomAddress(rng) - authenticatorAddr := testutils.RandomAddress(rng) - batcherAddr := crypto.PubkeyToAddress(batcherPriv.PublicKey) - signer := types.NewCancunSigner(big.NewInt(100)) - - // Fork activates at L1 origin time 1000. A single config is reused across all - // sub-tests; only ref.Time changes to cross the boundary. - espressoTime := uint64(1000) - dsCfg := DataSourceConfig{ - l1Signer: signer, - batchInboxAddress: batchInboxAddr, - rollupCfg: &rollup.Config{ - EspressoTime: &espressoTime, - BatchAuthenticatorAddress: authenticatorAddr, - }, - batchAuthCaches: NewBatchAuthCaches(), - } - - ctx := context.Background() - logger := testlog.Logger(t, log.LevelDebug) - - newBatchTx := func(t *testing.T, author *ecdsa.PrivateKey, data []byte) *types.Transaction { - t.Helper() - tx, err := types.SignNewTx(author, signer, &types.DynamicFeeTx{ - ChainID: big.NewInt(100), Nonce: 0, Gas: 100_000, - GasTipCap: big.NewInt(2 * params.GWei), GasFeeCap: big.NewInt(30 * params.GWei), - To: &batchInboxAddr, Data: data, - }) - require.NoError(t, err) - return tx - } - - t.Run("pre-fork: batcher accepted via sender auth, no event scan", func(t *testing.T) { - // The empty mock asserts pre-fork derivation performs zero L1 receipt scanning: - // any FetchReceipts/L1BlockRefByHash call would be an unexpected call and panic. - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx := newBatchTx(t, batcherPriv, txData) - - ref := eth.L1BlockRef{Number: 1, Time: espressoTime - 1, Hash: testutils.RandomHash(rng)} - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 1, "pre-fork batcher tx should be accepted via sender-based auth") - require.Equal(t, eth.Data(txData), out[0]) - l1F.AssertExpectations(t) - }) - - t.Run("pre-fork: non-batcher sender rejected", func(t *testing.T) { - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx := newBatchTx(t, altAuthor, txData) - - ref := eth.L1BlockRef{Number: 1, Time: espressoTime - 1, Hash: testutils.RandomHash(rng)} - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 0, "pre-fork tx from a non-batcher sender should be rejected") - l1F.AssertExpectations(t) - }) - - t.Run("activation block: same batcher tx rejected without auth event", func(t *testing.T) { - // At the exact activation time (ref.Time == EspressoTime) the event-based path is - // active, so a sender-only batcher tx is no longer sufficient. - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx := newBatchTx(t, batcherPriv, txData) - - ref := eth.L1BlockRef{Number: 1, Time: espressoTime, Hash: testutils.RandomHash(rng)} - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, nil) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 0, "post-fork batcher tx without an auth event must be rejected") - l1F.AssertExpectations(t) - }) - - t.Run("activation block: same batcher tx accepted with auth event", func(t *testing.T) { - l1F := &testutils.MockL1Source{} - txData := testutils.RandomData(rng, 100) - tx := newBatchTx(t, batcherPriv, txData) - - ref := eth.L1BlockRef{Number: 1, Time: espressoTime, Hash: testutils.RandomHash(rng)} - batchHash := ComputeCalldataBatchHash(txData) - ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, []common.Hash{batchHash}) - - out, err := DataFromEVMTransactions(ctx, dsCfg, batcherAddr, types.Transactions{tx}, l1F, ref, logger) - require.NoError(t, err) - require.Len(t, out, 1, "post-fork batcher tx with a matching auth event must be accepted") - require.Equal(t, eth.Data(txData), out[0]) - l1F.AssertExpectations(t) - }) -} From 1e5dde9db5e1981f650ef97e13466974b652a769 Mon Sep 17 00:00:00 2001 From: Piers Powlesland Date: Thu, 2 Jul 2026 12:10:11 +0100 Subject: [PATCH 2/3] feat(rollup): reject espresso_time scheduled before ecotone_time Dropping the Espresso-aware calldata source made "espresso_time >= ecotone_time" a silent correctness invariant: Espresso event-based authentication now lives only on the post-ecotone blob path, so a block in the [espresso_time, ecotone_time) window would route to the pre-ecotone calldata source and silently fall back to sender-based authorization. Guard the invariant in Config.Check(): error with ErrEspressoBeforeEcotone when espresso_time is set but ecotone_time is unset or scheduled later than espresso_time. Every real Celo chain sets ecotone_time = 0, so this only fires on a misconfiguration -- but now it fails fast instead of corrupting derivation. Mirrors the celo-kona validate_espresso guard (EspressoBeforeEcotone). --- op-node/rollup/types.go | 20 ++++++++++++++---- op-node/rollup/types_test.go | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index a128bb52c47..47dd86cb1cb 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -40,6 +40,7 @@ var ( ErrL2ChainIDNotPositive = errors.New("L2 chain ID must be non-zero and positive") ErrMissingBatchAuthenticatorAddress = errors.New("missing batch authenticator address when Espresso is enabled") + ErrEspressoBeforeEcotone = errors.New("espresso_time must be greater than or equal to ecotone_time; Espresso batch authentication only runs on the post-ecotone blob data source") ) type Genesis struct { @@ -385,10 +386,21 @@ func (cfg *Config) Check() error { return err } - // When Espresso is enabled, batches must be authenticated via BatchInfoAuthenticated events - // emitted by the BatchAuthenticator contract, so a non-zero authenticator address is required. - if cfg.EspressoTime != nil && cfg.BatchAuthenticatorAddress == (common.Address{}) { - return ErrMissingBatchAuthenticatorAddress + if cfg.EspressoTime != nil { + // When Espresso is enabled, batches must be authenticated via BatchInfoAuthenticated + // events emitted by the BatchAuthenticator contract, so a non-zero authenticator + // address is required. + if cfg.BatchAuthenticatorAddress == (common.Address{}) { + return ErrMissingBatchAuthenticatorAddress + } + // Espresso event-based batch authentication only runs on the post-ecotone blob data + // source. If espresso_time were scheduled before ecotone_time (or ecotone were never + // scheduled), blocks in the [espresso_time, ecotone_time) window would route to the + // pre-ecotone calldata source and silently fall back to sender-based authorization. + // Every real Celo chain sets ecotone_time = 0, so this only guards a misconfiguration. + if cfg.EcotoneTime == nil || *cfg.EspressoTime < *cfg.EcotoneTime { + return ErrEspressoBeforeEcotone + } } return nil diff --git a/op-node/rollup/types_test.go b/op-node/rollup/types_test.go index 207b0215d2d..df58e6dcc14 100644 --- a/op-node/rollup/types_test.go +++ b/op-node/rollup/types_test.go @@ -584,7 +584,13 @@ func TestConfig_Check(t *testing.T) { { name: "EspressoEnabledWithBatchAuthenticatorAddress", modifier: func(cfg *Config) { + // Espresso requires ecotone (and its predecessor forks) to be active first. + zero := uint64(0) espressoTime := uint64(1) + cfg.RegolithTime = &zero + cfg.CanyonTime = &zero + cfg.DeltaTime = &zero + cfg.EcotoneTime = &zero cfg.EspressoTime = &espressoTime cfg.BatchAuthenticatorAddress = common.Address{0x01} }, @@ -663,6 +669,41 @@ func TestConfig_Check(t *testing.T) { } } +// TestConfig_Check_EspressoBeforeEcotone verifies that Check rejects an Espresso activation +// scheduled before ecotone. Espresso must activate at or after ecotone, because Espresso +// event-based batch authentication only runs on the post-ecotone blob data source. +func TestConfig_Check_EspressoBeforeEcotone(t *testing.T) { + auth := common.Address{0x12} + ptr := func(v uint64) *uint64 { return &v } + // withTimes builds a valid config with the given espresso and ecotone activation times. + // When ecotone is set, the predecessor forks (regolith..delta) are co-scheduled with it so + // the generic fork-ordering checks pass and the espresso/ecotone rule is isolated. + withTimes := func(espresso uint64, ecotone *uint64) *Config { + cfg := randConfig() + cfg.EspressoTime = ptr(espresso) + cfg.BatchAuthenticatorAddress = auth + if ecotone != nil { + cfg.RegolithTime = ecotone + cfg.CanyonTime = ecotone + cfg.DeltaTime = ecotone + cfg.EcotoneTime = ecotone + } + return cfg + } + + // espresso == ecotone: valid (the first espresso-active block is already post-ecotone). + require.NoError(t, withTimes(100, ptr(100)).Check()) + // espresso > ecotone: valid. + require.NoError(t, withTimes(100, ptr(50)).Check()) + // the real Celo case: ecotone at genesis, espresso scheduled later. + require.NoError(t, withTimes(1_000_000, ptr(0)).Check()) + + // espresso < ecotone: invalid — the [espresso, ecotone) window would bypass auth. + require.ErrorIs(t, withTimes(50, ptr(100)).Check(), ErrEspressoBeforeEcotone) + // espresso set but ecotone never scheduled: invalid (every espresso block bypasses auth). + require.ErrorIs(t, withTimes(50, nil).Check(), ErrEspressoBeforeEcotone) +} + func TestTimestampForBlock(t *testing.T) { config := randConfig() From 5432057442fc8808f05a0c0574a2ccd8523a3cf0 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Fri, 3 Jul 2026 10:46:58 +0200 Subject: [PATCH 3/3] test(derive): restore multi-tx event-auth coverage; review cleanups - add mixed-block and multi-commitment subtests to TestDataAndHashesFromTxsEventAuth, restoring per-tx commitment matching coverage lost with espresso_calldata_source_test.go - drop stale comment reference to the deleted TestDataFromEVMTransactionsForkBoundary - reuse u64ptr instead of a local ptr closure in TestConfig_Check_EspressoBeforeEcotone --- .../derive/espresso_blob_data_source_test.go | 80 ++++++++++++++++++- op-node/rollup/types_test.go | 11 ++- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/op-node/rollup/derive/espresso_blob_data_source_test.go b/op-node/rollup/derive/espresso_blob_data_source_test.go index e0923057a84..052801d263f 100644 --- a/op-node/rollup/derive/espresso_blob_data_source_test.go +++ b/op-node/rollup/derive/espresso_blob_data_source_test.go @@ -278,6 +278,82 @@ func TestDataAndHashesFromTxsEventAuth(t *testing.T) { require.Equal(t, 0, len(blobHashes)) l1F.AssertExpectations(t) }) + + t.Run("mixed block: only the event-authenticated tx accepted", func(t *testing.T) { + l1F := &testutils.MockL1Source{} + newInboxTx := func(key *ecdsa.PrivateKey) *types.Transaction { + tx, err := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: rng.Uint64(), + GasPrice: new(big.Int).SetUint64(rng.Uint64()), + Gas: 2_000_000, + To: &batchInboxAddr, + Value: big.NewInt(10), + Data: testutils.RandomData(rng, 200), + }) + require.NoError(t, err) + return tx + } + // tx1: batcher-signed, commitment authenticated — accepted. + // tx2: batcher-signed, commitment NOT authenticated — rejected even though its + // sender authenticated tx1's commitment (each tx must be matched against its + // own commitment, not just any commitment from an authenticated caller). + // tx3: unknown sender, not authenticated — rejected. + tx1 := newInboxTx(privateKey) + tx2 := newInboxTx(privateKey) + tx3 := newInboxTx(altKey) + + ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} + ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, + []common.Hash{ComputeCalldataBatchHash(tx1.Data())}) + + data, blobHashes, err := dataAndHashesFromTxs(ctx, types.Transactions{tx1, tx2, tx3}, &config, batcherAddr, l1F, ref, logger) + require.NoError(t, err) + require.Equal(t, 1, len(data), "only the event-authenticated tx should pass") + require.Equal(t, 0, len(blobHashes)) + require.NotNil(t, data[0].calldata) + require.Equal(t, eth.Data(tx1.Data()), *data[0].calldata) + require.NotEqual(t, eth.Data(tx2.Data()), *data[0].calldata) + l1F.AssertExpectations(t) + }) + + t.Run("multiple authenticated txs each accepted for their own commitment", func(t *testing.T) { + // One calldata batch and one blob batch in the same block, each authenticated + // via its own commitment (calldata hash vs blob-hash concatenation). Both must + // be accepted and mapped to their own data. + l1F := &testutils.MockL1Source{} + calldataTx, _ := types.SignNewTx(privateKey, signer, &types.LegacyTx{ + Nonce: rng.Uint64(), + GasPrice: new(big.Int).SetUint64(rng.Uint64()), + Gas: 2_000_000, + To: &batchInboxAddr, + Value: big.NewInt(10), + Data: testutils.RandomData(rng, 200), + }) + blobHash := testutils.RandomHash(rng) + blobTx, _ := types.SignNewTx(privateKey, signer, &types.BlobTx{ + Nonce: rng.Uint64(), + Gas: 2_000_000, + To: batchInboxAddr, + BlobHashes: []common.Hash{blobHash}, + }) + + ref := eth.L1BlockRef{Number: 1, Hash: testutils.RandomHash(rng)} + ref = mockAuthEvents(l1F, rng, ref, authenticatorAddr, batcherAddr, []common.Hash{ + ComputeCalldataBatchHash(calldataTx.Data()), + ComputeBlobBatchHash([]common.Hash{blobHash}), + }) + + data, blobHashes, err := dataAndHashesFromTxs(ctx, types.Transactions{calldataTx, blobTx}, &config, batcherAddr, l1F, ref, logger) + require.NoError(t, err) + require.Equal(t, 2, len(data)) + require.Equal(t, 1, len(blobHashes)) + require.Equal(t, blobHash, blobHashes[0]) + require.NotNil(t, data[0].calldata, "first entry must be the calldata batch") + require.Equal(t, eth.Data(calldataTx.Data()), *data[0].calldata) + require.Nil(t, data[1].calldata, "second entry must be the blob placeholder") + require.Nil(t, data[1].blob) + l1F.AssertExpectations(t) + }) } // TestDataAndHashesFromTxsForkBoundary exercises the Espresso fork gate flipping in the @@ -286,9 +362,7 @@ func TestDataAndHashesFromTxsEventAuth(t *testing.T) { // This is the path a chain with Ecotone active actually runs: OpenData always selects the // blob source, and calldata (type-2) batches flow through its non-blob branch. Pre-Espresso // (L1 origin time < EspressoTime) must use upstream sender-based authorization with no event -// scanning; at and after activation it must switch to event-based authentication. The gate is -// implemented separately here from the calldata source, so this mirrors -// TestDataFromEVMTransactionsForkBoundary to pin both copies. +// scanning; at and after activation it must switch to event-based authentication. func TestDataAndHashesFromTxsForkBoundary(t *testing.T) { rng := rand.New(rand.NewSource(7777)) privateKey := testutils.InsecureRandomKey(rng) diff --git a/op-node/rollup/types_test.go b/op-node/rollup/types_test.go index df58e6dcc14..ff8467a1a98 100644 --- a/op-node/rollup/types_test.go +++ b/op-node/rollup/types_test.go @@ -674,13 +674,12 @@ func TestConfig_Check(t *testing.T) { // event-based batch authentication only runs on the post-ecotone blob data source. func TestConfig_Check_EspressoBeforeEcotone(t *testing.T) { auth := common.Address{0x12} - ptr := func(v uint64) *uint64 { return &v } // withTimes builds a valid config with the given espresso and ecotone activation times. // When ecotone is set, the predecessor forks (regolith..delta) are co-scheduled with it so // the generic fork-ordering checks pass and the espresso/ecotone rule is isolated. withTimes := func(espresso uint64, ecotone *uint64) *Config { cfg := randConfig() - cfg.EspressoTime = ptr(espresso) + cfg.EspressoTime = u64ptr(espresso) cfg.BatchAuthenticatorAddress = auth if ecotone != nil { cfg.RegolithTime = ecotone @@ -692,14 +691,14 @@ func TestConfig_Check_EspressoBeforeEcotone(t *testing.T) { } // espresso == ecotone: valid (the first espresso-active block is already post-ecotone). - require.NoError(t, withTimes(100, ptr(100)).Check()) + require.NoError(t, withTimes(100, u64ptr(100)).Check()) // espresso > ecotone: valid. - require.NoError(t, withTimes(100, ptr(50)).Check()) + require.NoError(t, withTimes(100, u64ptr(50)).Check()) // the real Celo case: ecotone at genesis, espresso scheduled later. - require.NoError(t, withTimes(1_000_000, ptr(0)).Check()) + require.NoError(t, withTimes(1_000_000, u64ptr(0)).Check()) // espresso < ecotone: invalid — the [espresso, ecotone) window would bypass auth. - require.ErrorIs(t, withTimes(50, ptr(100)).Check(), ErrEspressoBeforeEcotone) + require.ErrorIs(t, withTimes(50, u64ptr(100)).Check(), ErrEspressoBeforeEcotone) // espresso set but ecotone never scheduled: invalid (every espresso block bypasses auth). require.ErrorIs(t, withTimes(50, nil).Check(), ErrEspressoBeforeEcotone) }