diff --git a/.gitmodules b/.gitmodules index 5422163a95994..909027324feab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "packages/contracts-bedrock/lib/solady-v0.0.245"] path = packages/contracts-bedrock/lib/solady-v0.0.245 url = https://github.com/vectorized/solady +[submodule "da-server"] + path = da-server + url = https://github.com/ethstorage/da-server diff --git a/da-server b/da-server new file mode 160000 index 0000000000000..a8fffd8bffd96 --- /dev/null +++ b/da-server @@ -0,0 +1 @@ +Subproject commit a8fffd8bffd96552cec1880090575adadd0ce43a diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 3401e5b5c6f9f..3e42a8e8fda4a 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 b8ea68f6a64cb..2fa4f30bf6d29 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-e2e/sgt/sgt_test.go b/op-e2e/sgt/sgt_test.go index f106c549ce530..19260176500ff 100644 --- a/op-e2e/sgt/sgt_test.go +++ b/op-e2e/sgt/sgt_test.go @@ -36,7 +36,7 @@ type BalanceCheck struct { vaultBalanceDiff *big.Int } -func TestDepositSGTBalance(t *testing.T) { +func TestSGT(t *testing.T) { op_e2e.InitParallel(t) sys, _ := faultproofs.StartFaultDisputeSystem(t) t.Cleanup(sys.Close) @@ -67,7 +67,7 @@ func TestDepositSGTBalance(t *testing.T) { action: sgtTxSuccess, }, { - name: "SGTTxWithoutNativeBalanceSuccess", + name: "FullSGTGasPaymentWithoutNativeBalanceSuccess", depositSgtValue: big.NewInt(10000000000000), depositL2Value: big.NewInt(0), txValue: big.NewInt(0), @@ -125,19 +125,15 @@ func TestDepositSGTBalance(t *testing.T) { for index, tCase := range tests { t.Run(tCase.name, func(t *testing.T) { b, err := tCase.action(ctx, t, int64(index), tCase.depositSgtValue, tCase.depositL2Value, tCase.txValue, sgt) + require.EqualValues(t, tCase.expectedErr, err) if tCase.depositSgtValue.Cmp(b.gasCost) >= 0 { - require.EqualValues(t, tCase.expectedErr, err) require.Equal(t, new(big.Int).Sub(tCase.depositSgtValue, b.gasCost).Cmp(b.sgtBalance), 0) require.Equal(t, tCase.expectedL2Balance.Cmp(b.l2Balance), 0) } else { balance := tCase.depositSgtValue.Add(tCase.depositSgtValue, tCase.depositL2Value) if balance.Cmp(b.gasCost) >= 0 { - require.EqualValues(t, tCase.expectedErr, err) require.Equal(t, int64(0), b.sgtBalance.Int64()) - require.Equal(t, balance.Sub(balance, b.gasCost).Cmp(b.l2Balance.Add(b.l2Balance, tCase.txValue)), 0) - } else { - require.EqualValues(t, tCase.expectedErr, err) } } if b.vaultBalanceDiff != nil { diff --git a/op-node/rollup/derive/altda_data_source_test.go b/op-node/rollup/derive/altda_data_source_test.go index b086b2db920bd..5ce4a4447c503 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 b907304d25910..11bb2a55ccb01 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 2f9be648c82b0..776428dce37b8 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 01b2616cca3fa..31555996ddbe3 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 8d064a7cdb8ca..0b23c7c18e63e 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 9e78aeae92301..60e61dbd8314f 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 diff --git a/ops-bedrock/docker-compose.yml b/ops-bedrock/docker-compose.yml index 285bf5566e4fe..66b17ea41af96 100644 --- a/ops-bedrock/docker-compose.yml +++ b/ops-bedrock/docker-compose.yml @@ -88,12 +88,28 @@ services: environment: GETH_MINER_RECOMMIT: 100ms + dac-server: + depends_on: + - l1 + - l1-bn + - l1-vc + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-node-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:devnet + ports: + - 8888:8888 + command: > + da-server da start --config /usr/local/bin/default.json + op-node: depends_on: - l1 - l1-bn - l1-vc - l2 + - dac-server build: context: ../ dockerfile: ops/docker/op-stack-go/Dockerfile @@ -129,7 +145,7 @@ services: --altda.enabled=${ALTDA_ENABLED} --altda.da-service=${ALTDA_SERVICE} --altda.da-server=http://da-server:3100 - --dac.urls= + --dac.urls=http://dac-server:8888 ports: - "7545:8545" - "9003:9003" diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index e7e183804ab97..21cb3790a3325 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -110,6 +110,10 @@ ARG OP_NODE_VERSION=v0.0.0 RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd op-chain-ops && make op-deployer \ GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_DEPLOYER_VERSION" +FROM --platform=$BUILDPLATFORM builder AS dac-server-builder +ARG OP_NODE_VERSION=v0.0.0 +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd da-server && go build -o da-server main.go + FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS cannon-target COPY --from=cannon-builder /app/cannon/bin/cannon /usr/local/bin/ COPY --from=cannon-builder /app/cannon/multicannon/embeds/* /usr/local/bin/ @@ -125,6 +129,8 @@ CMD ["op-wheel"] FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-node-target COPY --from=op-node-builder /app/op-node/bin/op-node /usr/local/bin/ +COPY --from=dac-server-builder /app/da-server/da-server /usr/local/bin/ +COPY --from=dac-server-builder /app/da-server/default.json /usr/local/bin/ CMD ["op-node"] FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-challenger-target diff --git a/ops/docker/op-stack-go/Dockerfile.dockerignore b/ops/docker/op-stack-go/Dockerfile.dockerignore index bd700e291e494..6913e78a483b8 100644 --- a/ops/docker/op-stack-go/Dockerfile.dockerignore +++ b/ops/docker/op-stack-go/Dockerfile.dockerignore @@ -18,5 +18,6 @@ !/op-supervisor !/op-wheel !/op-alt-da +!/da-server !/go.mod !/go.sum diff --git a/packages/contracts-bedrock/deploy-config/devnetL1-template.json b/packages/contracts-bedrock/deploy-config/devnetL1-template.json index 84eaf5f2655ed..4f196ea9f9a7f 100644 --- a/packages/contracts-bedrock/deploy-config/devnetL1-template.json +++ b/packages/contracts-bedrock/deploy-config/devnetL1-template.json @@ -49,7 +49,7 @@ "l2GenesisEcotoneTimeOffset": "0x0", "l2GenesisFjordTimeOffset": "0x0", "l2GenesisGraniteTimeOffset": "0x0", - "l2GenesisBlobTimeOffset": "0xffffffff", + "l2GenesisBlobTimeOffset": "0x0", "l1CancunTimeOffset": "0x0", "systemConfigStartBlock": 0, "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000001",