diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index 95d61e21ac132..dbe0294f178bd 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -740,6 +740,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, }, BatchAuthenticatorAddress: cfg.DeployConfig.BatchAuthenticatorAddress, + CeloEspressoTimestamp: func() *uint64 { v := uint64(cfg.DeployConfig.L1GenesisBlockTimestamp); return &v }(), } } defaultConfig := makeRollupConfig() diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 7191a752b4770..1c3f796fc699c 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -506,6 +506,14 @@ var ( Value: "0x703848f4c85f18e3acd8196c8ec91eb0b7bd0797", Category: OperationsCategory, } + CeloEspressoTimestamp = &cli.Uint64Flag{ + Name: "celo-espresso-timestamp", + Usage: "Unix timestamp for activating Celo Espresso integration features", + EnvVars: prefixEnvVars("CELO_ESPRESSO_TIMESTAMP"), + Category: RollupCategory, + Required: false, + Destination: new(uint64), + } ) var requiredFlags = []cli.Flag{ @@ -568,6 +576,7 @@ var optionalFlags = []cli.Flag{ CaffNodeHotShotUrls, CaffNodeEspressoLightClientAddr, CaffNodeL1EthRpc, + CeloEspressoTimestamp, } var DeprecatedFlags = []cli.Flag{ diff --git a/op-node/rollup/derive/blob_data_source.go b/op-node/rollup/derive/blob_data_source.go index ffcbad59539c7..6a3996b35396b 100644 --- a/op-node/rollup/derive/blob_data_source.go +++ b/op-node/rollup/derive/blob_data_source.go @@ -98,7 +98,7 @@ func (ds *BlobDataSource) open(ctx context.Context) ([]blobOrCalldata, error) { return nil, NewTemporaryError(fmt.Errorf("failed to open blob data source: L1 fetcher provided inconsistent number of receipts")) } - data, hashes := dataAndHashesFromTxs(txs, receipts, &ds.dsCfg, ds.batcherAddr, ds.log) + data, hashes := dataAndHashesFromTxs(txs, receipts, &ds.dsCfg, ds.ref, ds.batcherAddr, ds.log) if len(hashes) == 0 { // there are no blobs to fetch so we can return immediately @@ -127,14 +127,14 @@ func (ds *BlobDataSource) open(ctx context.Context) ([]blobOrCalldata, error) { // dataAndHashesFromTxs extracts calldata and datahashes from the input transactions and returns them. It // creates a placeholder blobOrCalldata element for each returned blob hash that must be populated // by fillBlobPointers after blob bodies are retrieved. -func dataAndHashesFromTxs(txs types.Transactions, receipts types.Receipts, config *DataSourceConfig, batcherAddr common.Address, logger log.Logger) ([]blobOrCalldata, []eth.IndexedBlobHash) { +func dataAndHashesFromTxs(txs types.Transactions, receipts types.Receipts, config *DataSourceConfig, ref eth.L1BlockRef, batcherAddr common.Address, logger log.Logger) ([]blobOrCalldata, []eth.IndexedBlobHash) { data := []blobOrCalldata{} var hashes []eth.IndexedBlobHash blobIndex := 0 // index of each blob in the block's blob sidecar for i, tx := range txs { receipt := receipts[i] // skip any non-batcher transactions - if !isValidBatchTx(tx, receipt, config.l1Signer, config.batchInboxAddress, batcherAddr, logger) { + if !isValidBatchTx(tx, receipt, ref, config.l1Signer, config.batchInboxAddress, batcherAddr, logger, config.celoEspressoTimestamp) { blobIndex += len(tx.BlobHashes()) continue } diff --git a/op-node/rollup/derive/blob_data_source_test.go b/op-node/rollup/derive/blob_data_source_test.go index cabbaeb3f45ec..4fa202f9d3a7a 100644 --- a/op-node/rollup/derive/blob_data_source_test.go +++ b/op-node/rollup/derive/blob_data_source_test.go @@ -57,7 +57,8 @@ func TestDataAndHashesFromTxs(t *testing.T) { } txs := types.Transactions{calldataTx} receipts := types.Receipts{calldataReceipt} - data, blobHashes := dataAndHashesFromTxs(txs, receipts, &config, batcherAddr, logger) + testRef := eth.L1BlockRef{Time: 1000} // Use a fixed timestamp for testing + data, blobHashes := dataAndHashesFromTxs(txs, receipts, &config, testRef, batcherAddr, logger) require.Equal(t, 1, len(data)) require.Equal(t, 0, len(blobHashes)) @@ -77,7 +78,7 @@ func TestDataAndHashesFromTxs(t *testing.T) { } txs = types.Transactions{blobTx} receipts = types.Receipts{blobReceipt} - data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, batcherAddr, logger) + data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, testRef, batcherAddr, logger) require.Equal(t, 1, len(data)) require.Equal(t, 1, len(blobHashes)) require.Nil(t, data[0].calldata) @@ -85,7 +86,7 @@ func TestDataAndHashesFromTxs(t *testing.T) { // try again with both the blob & calldata transactions and make sure both are picked up txs = types.Transactions{blobTx, calldataTx} receipts = types.Receipts{blobReceipt, calldataReceipt} - data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, batcherAddr, logger) + data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, testRef, batcherAddr, logger) require.Equal(t, 2, len(data)) require.Equal(t, 1, len(blobHashes)) require.NotNil(t, data[1].calldata) @@ -98,7 +99,7 @@ func TestDataAndHashesFromTxs(t *testing.T) { } txs = types.Transactions{blobTx} receipts = types.Receipts{blobReceipt} - data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, batcherAddr, logger) + data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, testRef, batcherAddr, logger) require.Equal(t, 0, len(data)) require.Equal(t, 0, len(blobHashes)) @@ -112,7 +113,7 @@ func TestDataAndHashesFromTxs(t *testing.T) { } txs = types.Transactions{blobTx} receipts = types.Receipts{blobReceipt} - data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, batcherAddr, logger) + data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, testRef, batcherAddr, logger) require.Equal(t, 0, len(data)) require.Equal(t, 0, len(blobHashes)) @@ -131,7 +132,7 @@ func TestDataAndHashesFromTxs(t *testing.T) { require.NoError(t, err) txs = types.Transactions{setCodeTx} receipts = types.Receipts{setCodeReceipt} - data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, batcherAddr, logger) + data, blobHashes = dataAndHashesFromTxs(txs, receipts, &config, testRef, batcherAddr, logger) require.Equal(t, 0, len(data)) require.Equal(t, 0, len(blobHashes)) } diff --git a/op-node/rollup/derive/calldata_source.go b/op-node/rollup/derive/calldata_source.go index 1ed52d88b2d6f..c8b0df91550f0 100644 --- a/op-node/rollup/derive/calldata_source.go +++ b/op-node/rollup/derive/calldata_source.go @@ -55,7 +55,7 @@ func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConf } return &CalldataSource{ open: true, - data: DataFromEVMTransactions(dsCfg, batcherAddr, txs, receipts, log.New("origin", ref)), + data: DataFromEVMTransactions(dsCfg, batcherAddr, txs, receipts, ref, log.New("origin", ref)), } } @@ -80,7 +80,7 @@ func (ds *CalldataSource) Next(ctx context.Context) (eth.Data, error) { return nil, NewTemporaryError(fmt.Errorf("failed to open calldata source: L1 fetcher provided inconsistent number of transactions and receipts")) } ds.open = true - ds.data = DataFromEVMTransactions(ds.dsCfg, ds.batcherAddr, txs, receipts, ds.log) + ds.data = DataFromEVMTransactions(ds.dsCfg, ds.batcherAddr, txs, receipts, ds.ref, ds.log) } if len(ds.data) == 0 { return nil, io.EOF @@ -94,10 +94,10 @@ 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. -func DataFromEVMTransactions(dsCfg DataSourceConfig, batcherAddr common.Address, txs types.Transactions, receipts types.Receipts, log log.Logger) []eth.Data { +func DataFromEVMTransactions(dsCfg DataSourceConfig, batcherAddr common.Address, txs types.Transactions, receipts types.Receipts, ref eth.L1BlockRef, log log.Logger) []eth.Data { out := []eth.Data{} for i, tx := range txs { - if isValidBatchTx(tx, receipts[i], dsCfg.l1Signer, dsCfg.batchInboxAddress, batcherAddr, log) { + if isValidBatchTx(tx, receipts[i], ref, dsCfg.l1Signer, dsCfg.batchInboxAddress, batcherAddr, log, dsCfg.celoEspressoTimestamp) { out = append(out, tx.Data()) } } diff --git a/op-node/rollup/derive/calldata_source_test.go b/op-node/rollup/derive/calldata_source_test.go index 416b10dcae0ab..d709dee7358f0 100644 --- a/op-node/rollup/derive/calldata_source_test.go +++ b/op-node/rollup/derive/calldata_source_test.go @@ -132,7 +132,8 @@ func TestDataFromEVMTransactions(t *testing.T) { } } - out := DataFromEVMTransactions(DataSourceConfig{cfg.L1Signer(), cfg.BatchInboxAddress, false}, batcherAddr, txs, receipts, testlog.Logger(t, log.LevelCrit)) + testRef := eth.L1BlockRef{Time: 1000} // Use a fixed timestamp for testing + out := DataFromEVMTransactions(DataSourceConfig{cfg.L1Signer(), cfg.BatchInboxAddress, false, nil}, batcherAddr, txs, receipts, testRef, 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 8bdf30734047e..68bb80a5db21a 100644 --- a/op-node/rollup/derive/data_source.go +++ b/op-node/rollup/derive/data_source.go @@ -50,9 +50,10 @@ type DataSourceFactory struct { func NewDataSourceFactory(log log.Logger, cfg *rollup.Config, fetcher L1Fetcher, blobsFetcher L1BlobsFetcher, altDAFetcher AltDAInputFetcher) *DataSourceFactory { config := DataSourceConfig{ - l1Signer: cfg.L1Signer(), - batchInboxAddress: cfg.BatchInboxAddress, - altDAEnabled: cfg.AltDAEnabled(), + l1Signer: cfg.L1Signer(), + batchInboxAddress: cfg.BatchInboxAddress, + altDAEnabled: cfg.AltDAEnabled(), + celoEspressoTimestamp: cfg.CeloEspressoTimestamp, } return &DataSourceFactory{ log: log, @@ -86,19 +87,25 @@ func (ds *DataSourceFactory) OpenData(ctx context.Context, ref eth.L1BlockRef, b // DataSourceConfig regroups the mandatory rollup.Config fields needed for DataFromEVMTransactions. type DataSourceConfig struct { - l1Signer types.Signer - batchInboxAddress common.Address - altDAEnabled bool + l1Signer types.Signer + batchInboxAddress common.Address + altDAEnabled bool + celoEspressoTimestamp *uint64 } -// isValidBatchTx returns true if: -// 1. the transaction is not reverted +// isValidBatchTx validates a transaction against the given configuration +// It returns true if: +// 1. the transaction is not reverted (only checked if CeloEspresso is enabled) // 2. the transaction type is any of Legacy, ACL, DynamicFee, Blob, or Deposit (for L3s). // 3. the transaction has a To() address that matches the batch inbox address, and // 4. the transaction has a valid signature from the batcher address -func isValidBatchTx(tx *types.Transaction, receipt *types.Receipt, l1Signer types.Signer, batchInboxAddr, batcherAddr common.Address, logger log.Logger) bool { - if receipt.Status != types.ReceiptStatusSuccessful { - return false +func isValidBatchTx(tx *types.Transaction, receipt *types.Receipt, l1Block eth.L1BlockRef, l1Signer types.Signer, batchInboxAddr, batcherAddr common.Address, logger log.Logger, celoEspressoTimestamp *uint64) bool { + // If CeloEspresso is activated, return false if the transaction is reverted + if celoEspressoTimestamp != nil && l1Block.Time >= uint64(*celoEspressoTimestamp) { + if receipt.Status != types.ReceiptStatusSuccessful { + logger.Info("tx is dropped since it is reverted", "hash", tx.Hash()) + return false + } } // For now, we want to disallow the SetCodeTx type or any future types. diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 617b07ea4f298..9f927977c9785 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -168,6 +168,11 @@ type Config struct { CaffNodeConfig CaffNodeConfig `json:"caff_node_config,omitempty"` BatchAuthenticatorAddress common.Address `json:"batch_authenticator_address,omitempty,omitzero"` + + // CeloEspressoTimestamp is the activation timestamp for Celo Espresso integration + // When this timestamp is reached, additional transaction validation rules in derivation pipeline will be enforced + // If nil, the integration is not active. + CeloEspressoTimestamp *uint64 `json:"celo_espresso_timestamp,omitempty"` } // CaffNodeConfig is the config for the Caff Node diff --git a/op-node/service.go b/op-node/service.go index 5ad0d2f3abbca..f04a73cdc6d8c 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -288,6 +288,10 @@ func applyOverrides(ctx *cli.Context, rollupConfig *rollup.Config) { interop := ctx.Uint64(opflags.InteropOverrideFlagName) rollupConfig.InteropTime = &interop } + if ctx.IsSet(flags.CeloEspressoTimestamp.Name) { + celoEspresso := ctx.Uint64(flags.CeloEspressoTimestamp.Name) + rollupConfig.CeloEspressoTimestamp = &celoEspresso + } } // applyCeloHardforks modifies the rollupConfig to apply Celo-specific hardforks.