diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 3401e5b5c6f9..3e42a8e8fda4 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -843,27 +843,32 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef // sendTx uses the txmgr queue to send the given transaction candidate after setting its // gaslimit. It will block if the txmgr queue has reached its MaxPendingTransactions limit. func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { - isEOAPointer := l.inboxIsEOA.Load() - if isEOAPointer == nil { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - defer cancel() - var code []byte - code, err := l.L1Client.CodeAt(ctx, *candidate.To, nil) - if err != nil { - l.Log.Error("CodeAt failed, assuming code exists", "err", err) - // assume code exist, but don't persist the result - isEOA := false - isEOAPointer = &isEOA - } else { - isEOA := len(code) == 0 - isEOAPointer = &isEOA - l.inboxIsEOA.Store(isEOAPointer) + var isEOAPointer *bool + if l.RollupConfig.UseInboxContract() { + // RollupConfig.UseInboxContract() being true just means the batcher's transaction status matters, + // but the actual inbox may still be an EOA. + isEOAPointer = l.inboxIsEOA.Load() + if isEOAPointer == nil { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + var code []byte + code, err := l.L1Client.CodeAt(ctx, *candidate.To, nil) + if err != nil { + l.Log.Error("CodeAt failed, assuming code exists", "err", err) + // assume code exist, but don't persist the result + isEOA := false + isEOAPointer = &isEOA + } else { + isEOA := len(code) == 0 + isEOAPointer = &isEOA + l.inboxIsEOA.Store(isEOAPointer) + } } - } + // Set GasLimit as intrinstic gas if the inbox is EOA, otherwise // Leave GasLimit unset when inbox is contract so that later on `EstimateGas` will be called - if *isEOAPointer { + if !l.RollupConfig.UseInboxContract() || *isEOAPointer { intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false) if err != nil { // we log instead of return an error here because txmgr can do its own gas estimation @@ -906,7 +911,7 @@ func (l *BatchSubmitter) handleReceipt(r txmgr.TxReceipt[txRef]) { l.recordFailedTx(r.ID.id, r.Err) } else { // check tx status - if r.Receipt.Status == types.ReceiptStatusFailed { + if l.RollupConfig.UseInboxContract() && r.Receipt.Status == types.ReceiptStatusFailed { l.recordFailedTx(r.ID.id, ErrInboxTransactionFailed) return } @@ -931,7 +936,9 @@ func (l *BatchSubmitter) recordFailedDARequest(id txID, err error) { } func (l *BatchSubmitter) recordFailedTx(id txID, err error) { - l.inboxIsEOA.Store(nil) + if l.RollupConfig.UseInboxContract() { + l.inboxIsEOA.Store(nil) + } l.Log.Warn("Transaction failed to send", logFields(id, err)...) l.state.TxFailed(id) } diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index b8ea68f6a64c..2fa4f30bf6d2 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -662,6 +662,7 @@ type L2InitializationConfig struct { L2CoreDeployConfig AltDADeployConfig SoulGasTokenConfig + InboxContractConfig } func (d *L2InitializationConfig) Check(log log.Logger) error { @@ -843,6 +844,12 @@ type SoulGasTokenConfig struct { IsSoulBackedByNative bool `json:"isSoulBackedByNative,omitempty"` } +// InboxContractConfig configures whether inbox contract is enabled. +// If enabled, the batcher tx will be further filtered by tx status. +type InboxContractConfig struct { + UseInboxContract bool `json:"useInboxContract,omitempty"` +} + // DependencyContext is the contextual configuration needed to verify the L1 dependencies, // used by DeployConfig.CheckAddresses. type DependencyContext struct { @@ -1011,6 +1018,10 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa L2BlobTime: d.L2BlobTime(l1StartTime), } } + var inboxContractConfig *rollup.InboxContractConfig + if d.UseInboxContract { + inboxContractConfig = &rollup.InboxContractConfig{UseInboxContract: true} + } return &rollup.Config{ Genesis: rollup.Genesis{ @@ -1050,6 +1061,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa ProtocolVersionsAddress: d.ProtocolVersionsProxy, AltDAConfig: altDA, L2BlobConfig: l2BlobConfig, + InboxContractConfig: inboxContractConfig, }, nil } diff --git a/op-node/rollup/derive/altda_data_source_test.go b/op-node/rollup/derive/altda_data_source_test.go index b086b2db920b..5ce4a4447c50 100644 --- a/op-node/rollup/derive/altda_data_source_test.go +++ b/op-node/rollup/derive/altda_data_source_test.go @@ -106,8 +106,6 @@ func TestAltDADataSource(t *testing.T) { nc := 0 firstChallengeExpirationBlock := uint64(95) - // for reusing after pipeline is reset - successfulReceipts := make(map[common.Hash]types.Receipts) for i := uint64(0); i <= pcfg.ChallengeWindow+pcfg.ResolveWindow; i++ { parent := l1Refs[len(l1Refs)-1] // create a new mock l1 ref @@ -119,6 +117,8 @@ func TestAltDADataSource(t *testing.T) { } l1Refs = append(l1Refs, ref) logger.Info("new l1 block", "ref", ref) + // called for each l1 block to sync challenges + l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) // pick a random number of commitments to include in the l1 block c := rng.Intn(4) @@ -149,12 +149,6 @@ func TestAltDADataSource(t *testing.T) { txs = append(txs, tx) } - - successfulReceipts[ref.Hash] = successfulReceiptsForTxs(txs) - // called by `getTxSucceed` to fetch tx status - l1F.ExpectFetchReceipts(ref.Hash, nil, successfulReceipts[ref.Hash], nil) - // called for each l1 block to sync challenges - l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) logger.Info("included commitments", "count", c) l1F.ExpectInfoAndTxsByHash(ref.Hash, testutils.RandomBlockInfo(rng), txs, nil) // called once per derivation @@ -211,8 +205,6 @@ func TestAltDADataSource(t *testing.T) { ref = l1Refs[i] logger.Info("re deriving block", "ref", ref, "i", i) - // called by `getTxSucceed` to fetch tx status - l1F.ExpectFetchReceipts(ref.Hash, nil, successfulReceipts[ref.Hash], nil) if i == len(l1Refs)-1 { l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) } @@ -228,6 +220,8 @@ func TestAltDADataSource(t *testing.T) { } l1Refs = append(l1Refs, ref) logger.Info("new l1 block", "ref", ref) + // called for each l1 block to sync challenges + l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) // pick a random number of commitments to include in the l1 block c := rng.Intn(4) @@ -257,10 +251,6 @@ func TestAltDADataSource(t *testing.T) { txs = append(txs, tx) } - // called by `getTxSucceed` to fetch tx status - l1F.ExpectFetchReceipts(ref.Hash, nil, successfulReceiptsForTxs(txs), nil) - // called for each l1 block to sync challenges - l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) logger.Info("included commitments", "count", c) l1F.ExpectInfoAndTxsByHash(ref.Hash, testutils.RandomBlockInfo(rng), txs, nil) } @@ -292,13 +282,6 @@ func TestAltDADataSource(t *testing.T) { finalitySignal.AssertExpectations(t) } -func successfulReceiptsForTxs(txs []*types.Transaction) (receipts types.Receipts) { - for _, tx := range txs { - receipts = append(receipts, &types.Receipt{TxHash: tx.Hash(), Status: types.ReceiptStatusSuccessful}) - } - return -} - // This tests makes sure the pipeline returns a temporary error if data is not found. func TestAltDADataSourceStall(t *testing.T) { logger := testlog.Logger(t, log.LevelDebug) @@ -367,6 +350,7 @@ func TestAltDADataSourceStall(t *testing.T) { ParentHash: parent.Hash, Time: parent.Time + l1Time, } + l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) // mock input commitments in l1 transactions input := testutils.RandomData(rng, 2000) comm, _ := storage.SetInput(ctx, input) @@ -385,9 +369,6 @@ func TestAltDADataSourceStall(t *testing.T) { txs := []*types.Transaction{tx} - // called by `getTxSucceed` to fetch tx status - l1F.ExpectFetchReceipts(ref.Hash, nil, successfulReceiptsForTxs(txs), nil) - l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) l1F.ExpectInfoAndTxsByHash(ref.Hash, testutils.RandomBlockInfo(rng), txs, nil) // delete the input from the DA provider so it returns not found @@ -491,6 +472,7 @@ func TestAltDADataSourceInvalidData(t *testing.T) { ParentHash: parent.Hash, Time: parent.Time + l1Time, } + l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) // mock input commitments in l1 transactions with an oversized input input := testutils.RandomData(rng, altda.MaxInputSize+1) comm, _ := storage.SetInput(ctx, input) @@ -538,9 +520,6 @@ func TestAltDADataSourceInvalidData(t *testing.T) { txs := []*types.Transaction{tx1, tx2, tx3} - // called by `getTxSucceed` to fetch tx status - l1F.ExpectFetchReceipts(ref.Hash, nil, successfulReceiptsForTxs(txs), nil) - l1F.ExpectFetchReceipts(ref.Hash, nil, types.Receipts{}, nil) l1F.ExpectInfoAndTxsByHash(ref.Hash, testutils.RandomBlockInfo(rng), txs, nil) src, err := factory.OpenData(ctx, ref, batcherAddr) diff --git a/op-node/rollup/derive/blob_data_source.go b/op-node/rollup/derive/blob_data_source.go index b907304d2591..11bb2a55ccb0 100644 --- a/op-node/rollup/derive/blob_data_source.go +++ b/op-node/rollup/derive/blob_data_source.go @@ -73,8 +73,12 @@ func (ds *BlobDataSource) Next(ctx context.Context) (eth.Data, error) { return data, nil } -// getTxSucceed returns a non-nil map which contains all successful tx hashes to batch inbox -func getTxSucceed(ctx context.Context, fetcher L1Fetcher, hash common.Hash, txs types.Transactions) (successTxs types.Transactions, err error) { +// getTxSucceed returns all successful txs +func getTxSucceed(ctx context.Context, useInboxContract bool, fetcher L1Fetcher, hash common.Hash, txs types.Transactions) (successTxs types.Transactions, err error) { + if !useInboxContract { + // if !useInboxContract, all txs are considered successful + return txs, nil + } _, receipts, err := fetcher.FetchReceipts(ctx, hash) if err != nil { return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err)) @@ -107,7 +111,7 @@ func (ds *BlobDataSource) open(ctx context.Context) ([]blobOrCalldata, error) { } return nil, NewTemporaryError(fmt.Errorf("failed to open blob data source: %w", err)) } - txs, err = getTxSucceed(ctx, ds.fetcher, ds.ref.Hash, txs) + txs, err = getTxSucceed(ctx, ds.dsCfg.useInboxContract, ds.fetcher, ds.ref.Hash, txs) if err != nil { return nil, err } diff --git a/op-node/rollup/derive/calldata_source.go b/op-node/rollup/derive/calldata_source.go index 2f9be648c82b..776428dce37b 100644 --- a/op-node/rollup/derive/calldata_source.go +++ b/op-node/rollup/derive/calldata_source.go @@ -44,7 +44,7 @@ func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConf batcherAddr: batcherAddr, } } - txs, err = getTxSucceed(ctx, fetcher, ref.Hash, txs) + txs, err = getTxSucceed(ctx, dsCfg.useInboxContract, fetcher, ref.Hash, txs) if err != nil { return &CalldataSource{ open: false, @@ -67,7 +67,7 @@ func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConf func (ds *CalldataSource) Next(ctx context.Context) (eth.Data, error) { if !ds.open { if _, txs, err := ds.fetcher.InfoAndTxsByHash(ctx, ds.ref.Hash); err == nil { - txs, err := getTxSucceed(ctx, ds.fetcher, ds.ref.Hash, txs) + txs, err := getTxSucceed(ctx, ds.dsCfg.useInboxContract, ds.fetcher, ds.ref.Hash, txs) if err != nil { return nil, err } diff --git a/op-node/rollup/derive/calldata_source_test.go b/op-node/rollup/derive/calldata_source_test.go index 01b2616cca3f..31555996ddbe 100644 --- a/op-node/rollup/derive/calldata_source_test.go +++ b/op-node/rollup/derive/calldata_source_test.go @@ -121,7 +121,7 @@ func TestDataFromEVMTransactions(t *testing.T) { } } - out := DataFromEVMTransactions(DataSourceConfig{cfg.L1Signer(), cfg.BatchInboxAddress, false}, batcherAddr, txs, testlog.Logger(t, log.LevelCrit)) + out := DataFromEVMTransactions(DataSourceConfig{cfg.L1Signer(), cfg.BatchInboxAddress, false, false}, 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 8d064a7cdb8c..0b23c7c18e63 100644 --- a/op-node/rollup/derive/data_source.go +++ b/op-node/rollup/derive/data_source.go @@ -52,6 +52,7 @@ func NewDataSourceFactory(log log.Logger, cfg *rollup.Config, fetcher L1Fetcher, l1Signer: cfg.L1Signer(), batchInboxAddress: cfg.BatchInboxAddress, altDAEnabled: cfg.AltDAEnabled(), + useInboxContract: cfg.UseInboxContract(), } return &DataSourceFactory{ log: log, @@ -88,6 +89,7 @@ type DataSourceConfig struct { l1Signer types.Signer batchInboxAddress common.Address altDAEnabled bool + useInboxContract bool } // isValidBatchTx returns true if: diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 9e78aeae9230..60e61dbd8314 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -140,18 +140,28 @@ type Config struct { // AltDAConfig. We are in the process of migrating to the AltDAConfig from these legacy top level values AltDAConfig *AltDAConfig `json:"alt_da,omitempty"` - L2BlobConfig *L2BlobConfig `json:"l2_blob_config,omitempty"` + L2BlobConfig *L2BlobConfig `json:"l2_blob_config,omitempty"` + InboxContractConfig *InboxContractConfig `json:"inbox_contract_config,omitempty"` } type L2BlobConfig struct { L2BlobTime *uint64 `json:"l2BlobTime,omitempty"` } +type InboxContractConfig struct { + UseInboxContract bool `json:"use_inbox_contract,omitempty"` +} + // IsL2Blob returns whether l2 blob is enabled func (cfg *Config) IsL2Blob(parentTime uint64) bool { return cfg.IsL2BlobTimeSet() && *cfg.L2BlobConfig.L2BlobTime <= parentTime } +// UseInboxContract returns whether inbox contract is enabled +func (cfg *Config) UseInboxContract() bool { + return cfg.InboxContractConfig != nil && cfg.InboxContractConfig.UseInboxContract +} + // IsL2BlobTimeSet returns whether l2 blob activation time is set func (cfg *Config) IsL2BlobTimeSet() bool { return cfg.L2BlobConfig != nil && cfg.L2BlobConfig.L2BlobTime != nil