Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions op-acceptance-tests/tests/altda/altda_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package altda

import (
"context"
"testing"

"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-devstack/stack/match"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

// TestAltDA_SafeHeadProgresses verifies the full AltDA pipeline:
// 1. Batcher posts batch data to the DA server
// 2. Batcher posts only the commitment to L1
// 3. Op-node reads commitment from L1
// 4. Op-node fetches batch data from DA server using the commitment
// 5. Op-node derives the batch and advances the safe head
//
// If the safe head advances after an L2 transaction, the entire AltDA
// derivation pipeline is proven to work end-to-end.
func TestAltDA_SafeHeadProgresses(gt *testing.T) {
t := devtest.SerialT(gt)
sys := presets.NewMinimal(t)
l := t.Logger()

// Record initial safe head
initialSafe := sys.L2CL.HeadBlockRef(types.LocalSafe)
l.Info("Initial safe head", "number", initialSafe.Number, "hash", initialSafe.Hash)

// Send an L2 transaction
alice := sys.FunderL2.NewFundedEOA(eth.OneTenthEther)
bob := sys.Wallet.NewEOA(sys.L2EL)
alice.Transfer(bob.Address(), eth.OneHundredthEther)

// Wait for safe head to advance — this is the critical assertion.
// Safe head can only advance if the batcher submitted a commitment,
// and op-node resolved it via the DA server.
dsl.CheckAll(t, sys.L2CL.AdvancedFn(types.LocalSafe, 1, 30))

// Log final sync status as evidence
status := sys.L2CL.SyncStatus()
l.Info("AltDA chain progressed successfully",
"unsafeL2", status.UnsafeL2.Number,
"safeL2", status.SafeL2.Number,
"localSafeL2", status.LocalSafeL2.Number,
)

// Verify L1 batch transactions contain commitments (start with 0x01),
// not raw batch data (which starts with 0x00).
verifyL1CommitmentData(t, sys)
}

// verifyL1CommitmentData scans recent L1 blocks for batcher transactions
// and verifies they contain AltDA commitment data (version byte 0x01).
func verifyL1CommitmentData(t devtest.T, sys *presets.Minimal) {
l := t.Logger()
rollupCfg := sys.L2Chain.Escape().RollupConfig()
batchInbox := rollupCfg.BatchInboxAddress

l1EC := sys.L1Network.Escape().L1ELNode(match.FirstL1EL).EthClient()

ctx, cancel := context.WithTimeout(t.Ctx(), dsl.DefaultTimeout)
defer cancel()

head, err := l1EC.BlockRefByLabel(ctx, eth.Unsafe)
require.NoError(t, err)

// Scan backwards through recent L1 blocks looking for batcher txs
scanFloor := uint64(0)
if head.Number > 20 {
scanFloor = head.Number - 20
}
foundCommitment := false
for blockNum := head.Number; blockNum > scanFloor; blockNum-- {
_, txs, err := l1EC.InfoAndTxsByNumber(ctx, blockNum)
require.NoError(t, err)

for _, tx := range txs {
if tx.To() == nil || *tx.To() != batchInbox {
continue
}
data := tx.Data()
if len(data) == 0 {
continue
}
// In AltDA mode, batcher prefixes commitment data with DerivationVersion1 (0x01).
// Regular calldata batches use DerivationVersion0 (0x00).
version := data[0]
l.Info("Found batcher tx on L1",
"block", blockNum,
"txHash", tx.Hash(),
"dataLen", len(data),
"versionByte", version,
)
require.Equal(t, byte(0x01), version,
"batcher tx should contain AltDA commitment (version 0x01), got version 0x%02x", version)
foundCommitment = true
}
}
require.True(t, foundCommitment, "should find at least one batcher commitment tx on L1")
l.Info("L1 commitment data verified — batcher posted commitments, not raw batch data")
}
60 changes: 60 additions & 0 deletions op-acceptance-tests/tests/altda/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package altda

import (
"testing"

"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-devstack/sysgo"
)

// minimalSystemNoChallenger mirrors DefaultMinimalSystem but omits the L2 challenger
// (which requires the cannon binary) since AltDA tests don't need fault proofs.
func minimalSystemNoChallenger() stack.Option[*sysgo.Orchestrator] {
ids := sysgo.NewDefaultMinimalSystemIDs(sysgo.DefaultL1ID, sysgo.DefaultL2AID)
dest := &sysgo.DefaultMinimalSystemIDs{}

opt := stack.Combine[*sysgo.Orchestrator]()

opt.Add(stack.BeforeDeploy(func(o *sysgo.Orchestrator) {
o.P().Logger().Info("Setting up (AltDA, no challenger)")
}))

opt.Add(sysgo.WithMnemonicKeys(devkeys.TestMnemonic))

opt.Add(sysgo.WithDeployer(),
sysgo.WithDeployerOptions(
sysgo.WithLocalContractSources(),
sysgo.WithCommons(ids.L1.ChainID()),
sysgo.WithPrefundedL2(ids.L1.ChainID(), ids.L2.ChainID()),
),
)

opt.Add(sysgo.WithL1Nodes(ids.L1EL, ids.L1CL))

opt.Add(sysgo.WithL2ELNode(ids.L2EL))
opt.Add(sysgo.WithL2CLNode(ids.L2CL, ids.L1CL, ids.L1EL, ids.L2EL, sysgo.L2CLSequencer()))

opt.Add(sysgo.WithBatcher(ids.L2Batcher, ids.L1EL, ids.L2CL, ids.L2EL))
opt.Add(sysgo.WithProposer(ids.L2Proposer, ids.L1EL, &ids.L2CL, nil))

opt.Add(sysgo.WithFaucets([]stack.L1ELNodeID{ids.L1EL}, []stack.L2ELNodeID{ids.L2EL}))

opt.Add(sysgo.WithTestSequencer(ids.TestSequencer, ids.L1CL, ids.L2CL, ids.L1EL, ids.L2EL))

// Deliberately omit WithL2Challenger — AltDA tests don't need fault proofs.

opt.Add(stack.Finally(func(orch *sysgo.Orchestrator) {
*dest = ids
}))

return opt
}

func TestMain(m *testing.M) {
presets.DoMain(m,
stack.MakeCommon(sysgo.WithAltDA(sysgo.DefaultL2AID)),
stack.MakeCommon[*sysgo.Orchestrator](minimalSystemNoChallenger()),
)
}
77 changes: 77 additions & 0 deletions op-devstack/sysgo/da_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package sysgo

import (
"os"

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

altda "github.com/ethereum-optimism/optimism/op-alt-da"
bss "github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth"
)

// WithAltDA starts a DA server and configures the batcher and op-node to use AltDA mode
// with Keccak256 commitments. The DA server computes keccak256(data) as the commitment,
// which the Rust AltDA data source can verify inside the zkVM proof.
//
// The DA server is started and batcher/L2CL options are accumulated during the Deploy phase.
// The rollup config is overridden via L2CLConfig.RollupAltDAConfig, which is applied
// inside op-node's AfterDeploy hook (after the deployer registers the L2 network).
// This avoids ordering dependencies with the deployer's AfterDeploy.
func WithAltDA(l2ChainID eth.ChainID) stack.Option[*Orchestrator] {
return stack.Deploy(func(orch *Orchestrator) {
p := orch.P()
logger := p.Logger()

// Start DA server with in-memory storage (Keccak256 commitment mode)
store := altda.NewMemStore()
server := altda.NewDAServer("127.0.0.1", 0, store, logger, false)
p.Require().NoError(server.Start(), "failed to start DA server")
p.Cleanup(func() {
logger.Info("Stopping DA server")
_ = server.Stop()
})

endpoint := server.HttpEndpoint()
logger.Info("Started AltDA server", "endpoint", endpoint)

// Export DA server URL for op-succinct proposer subprocess.
// The Rust host reads ALTDA_SERVER_URL from env to fetch batch data.
os.Setenv("ALTDA_SERVER_URL", endpoint)

altDACLICfg := altda.CLIConfig{
Enabled: true,
DAServerURL: endpoint,
VerifyOnRead: true,
GenericDA: false,
}

// Keccak256 mode requires a non-zero DAChallengeAddress to pass rollup config validation.
// The address is unused — challenges are never triggered in e2e tests.
altDAConfig := &rollup.AltDAConfig{
CommitmentType: altda.KeccakCommitmentString,
DAChallengeAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
DAChallengeWindow: 10,
DAResolveWindow: 10,
}

// Append batcher option — consumed by WithBatcher's AfterDeploy
orch.batcherOptions = append(orch.batcherOptions, func(id stack.L2BatcherID, cfg *bss.CLIConfig) {
if id.ChainID() == l2ChainID {
cfg.AltDA = altDACLICfg
}
})

// Append L2CL option — consumed by WithOpNode's AfterDeploy.
// Sets both the CLI config and the rollup config override.
orch.l2CLOptions = append(orch.l2CLOptions, L2CLOptionFn(func(p devtest.P, id stack.L2CLNodeID, cfg *L2CLConfig) {
if id.ChainID() == l2ChainID {
cfg.AltDA = altDACLICfg
cfg.RollupAltDAConfig = altDAConfig
}
}))
})
}
23 changes: 19 additions & 4 deletions op-devstack/sysgo/deployer_succinct.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ func (o *Orchestrator) deployOpSuccinctL2OutputOracle(
return "", fmt.Errorf("failed to write L1 chain config: %w", err)
}

addr, err := execDeployOracle(o.P(), repoRoot, envFile)
addr, err := execDeployOracle(o.P(), repoRoot, envFile, cfgs.Features)
if err != nil {
return "", err
}
Expand All @@ -373,9 +373,15 @@ func (o *Orchestrator) deployOpSuccinctL2OutputOracle(
return addr, nil
}

// execDeployOracle runs `just deploy-oracle <envFile>` and parses the output
func execDeployOracle(p devtest.P, repoRoot, envFile string) (string, error) {
cmd := exec.CommandContext(p.Ctx(), "just", "deploy-oracle", envFile)
// execDeployOracle runs `just deploy-oracle <envFile> [features]` and parses the output.
// If features is non-empty, it is passed as an extra argument so that deploy-oracle
// compiles fetch-l2oo-config with the matching Cargo feature flags (e.g., "altda").
func execDeployOracle(p devtest.P, repoRoot, envFile, features string) (string, error) {
args := []string{"deploy-oracle", envFile}
if features != "" {
args = append(args, features)
}
cmd := exec.CommandContext(p.Ctx(), "just", args...)
cmd.Dir = repoRoot

logger := p.Logger().New("component", "succinct-deployer")
Expand All @@ -394,6 +400,7 @@ type L2OOConfigs struct {
SubmissionInterval *uint64
RangeProofInterval *uint64
FinalizationPeriodSecs *uint64
Features string // Cargo features for deploy-oracle (e.g., "altda")
}

type L2OOOption func(*L2OOConfigs)
Expand Down Expand Up @@ -428,6 +435,14 @@ func WithL2OOFinalizationPeriodSecs(n uint64) L2OOOption {
}
}

// WithL2OOFeatures sets Cargo features for the deploy-oracle step (e.g., "altda").
// This ensures the vkey commitment matches the proposer binary's feature flags.
func WithL2OOFeatures(features string) L2OOOption {
return func(cfg *L2OOConfigs) {
cfg.Features = features
}
}

const defaultFinalizationPeriodSecs = 3600

// resolveFinalizationPeriodSecs returns the configured finalization period or the default.
Expand Down
9 changes: 9 additions & 0 deletions op-devstack/sysgo/l2_cl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package sysgo
import (
"os"

altda "github.com/ethereum-optimism/optimism/op-alt-da"
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-node/rollup"
nodeSync "github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
Expand Down Expand Up @@ -36,6 +38,13 @@ type L2CLConfig struct {

// NoDiscovery is the flag to enable/disable discovery
NoDiscovery bool

// AltDA is the AltDA CLI configuration for this L2 CL node.
AltDA altda.CLIConfig

// RollupAltDAConfig, when set, overrides the rollup config's AltDAConfig.
// This allows AltDA to be enabled without modifying the L2 network's rollup config directly.
RollupAltDAConfig *rollup.AltDAConfig
}

func L2CLSequencer() L2CLOption {
Expand Down
6 changes: 4 additions & 2 deletions op-devstack/sysgo/l2_cl_opnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"

altda "github.com/ethereum-optimism/optimism/op-alt-da"
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/shim"
Expand Down Expand Up @@ -301,10 +300,13 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L
ConductorEnabled: false,
ConductorRpc: nil,
ConductorRpcTimeout: 0,
AltDA: altda.CLIConfig{},
AltDA: cfg.AltDA,
IgnoreMissingPectraBlobSchedule: false,
ExperimentalOPStackAPI: true,
}
if cfg.RollupAltDAConfig != nil {
nodeCfg.Rollup.AltDAConfig = cfg.RollupAltDAConfig
}
if cfg.SafeDBPath != "" {
nodeCfg.SafeDBPath = cfg.SafeDBPath
}
Expand Down