Skip to content
Merged
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
2 changes: 1 addition & 1 deletion op-chain-ops/interopgen/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func CreateL1(logger log.Logger, fa *foundry.ArtifactsFS, srcFS *foundry.SourceM
PrevRandao: cfg.L1GenesisBlockMixHash,
BlobHashes: nil,
}
l1Host := script.NewHost(logger.New("role", "l1", "chain", cfg.ChainID), fa, srcFS, l1Context, script.WithCreate2Deployer())
l1Host := script.NewHost(logger.New("role", "l1", "chain", cfg.ChainID), fa, srcFS, l1Context, script.WithCreate2Deployer(), script.WithNoMaxCodeSize())
return l1Host
}

Expand Down
2 changes: 1 addition & 1 deletion op-e2e/actions/helpers/l2_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher,

var interopSys interop.SubSystem
if cfg.InteropTime != nil {
mm := indexing.NewIndexingMode(log, cfg, "127.0.0.1", 0, interopJWTSecret, l1, eng, &opmetrics.NoopRPCMetrics{})
mm := indexing.NewIndexingMode(log, cfg, "127.0.0.1", 0, interopJWTSecret, l1, eng, &opmetrics.NoopRPCMetrics{}, 5_000)
mm.TestDisableEventDeduplication()
interopSys = mm
sys.Register("interop", interopSys, opts)
Expand Down
31 changes: 31 additions & 0 deletions op-e2e/actions/interop/dsl/dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,34 @@ func (d *InteropDSL) ActSyncSupernode(t helpers.Testing, opts ...actSyncSupernod
chain.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head.
}
}

// SubmitBatchesAndAdvanceL1 submits the latest batches for all chains and synchronizes supervisor the latest data on L1
func (d *InteropDSL) SubmitBatchesAndAdvanceL1(t helpers.Testing, l1BlockTimeSeconds uint64) {
d.Actors.ChainA.Batcher.ActSubmitAll(t)
d.Actors.ChainB.Batcher.ActSubmitAll(t)

d.AdvanceL1(
func(o *AdvanceL1Opts) {
o.L1BlockTimeSeconds = l1BlockTimeSeconds
},
WithActIncludeTx(d.Actors.L1Miner.ActL1IncludeTx(d.Actors.ChainA.BatcherAddr)),
WithActIncludeTx(d.Actors.L1Miner.ActL1IncludeTx(d.Actors.ChainB.BatcherAddr)),
)

for _, chain := range []*Chain{d.Actors.ChainA, d.Actors.ChainB} {
status := chain.Sequencer.SyncStatus()
require.Equalf(t, status.UnsafeL2, status.LocalSafeL2, "Chain %v did not fully advance local safe head", chain.ChainID)
chain.Sequencer.SyncSupervisor(t)
}
d.Actors.Supervisor.ProcessFull(t)
}

// SubmitBatches checks if there are any pending batches to be submitted for either chain,
// and if so, submits them and advances L1 to process them.
func (d *InteropDSL) SubmitBatches(t helpers.Testing, l1BlockTimeSeconds uint64) {
statusA := d.Actors.ChainA.Sequencer.SyncStatus()
statusB := d.Actors.ChainB.Sequencer.SyncStatus()
if statusA.UnsafeL2.Number != statusA.LocalSafeL2.Number || statusB.UnsafeL2.Number != statusB.LocalSafeL2.Number {
d.SubmitBatchesAndAdvanceL1(t, l1BlockTimeSeconds)
}
}
117 changes: 115 additions & 2 deletions op-e2e/actions/interop/proofs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,109 @@ func TestInteropFaultProofs_DepositMessage_InvalidExecution(gt *testing.T) {
runFppAndChallengerTests(gt, system, tests)
}

func TestInteropFaultProofs_DeepCanonicalBlockQuery(gt *testing.T) {
const totalBlocks = 10_000
const blocksPerBatch = 200
const expiryTime = totalBlocks * 4 // to ensure xchain messages don't expire

t := helpers.NewDefaultTesting(gt)
system := dsl.NewInteropDSL(t, dsl.SetMessageExpiryTime(expiryTime))
actors := system.Actors
emitter := system.DeployEmitterContracts()

// Keep L1 time aligned with L2 time, otherwise MaxSequencerDrift will stall block building.
// We assume both L2s share the same block time in this test.
require.Equal(t, actors.ChainA.RollupCfg.BlockTime, actors.ChainB.RollupCfg.BlockTime)
l1BlockTimeSeconds := uint64(blocksPerBatch) * actors.ChainA.RollupCfg.BlockTime
require.NotZero(t, l1BlockTimeSeconds)

var msg *dsl.Message
var emitBlockNum uint64
for i := range totalBlocks {
if i == 10 {
actors.ChainA.Sequencer.ActL2StartBlock(t)
msg = dsl.NewMessage(system, actors.ChainA, emitter, "early message").Emit()
actors.ChainA.Sequencer.ActL2EndBlock(t)
msg.CheckEmitted()
emitBlockNum = actors.ChainA.Sequencer.L2Unsafe().Number
} else {
actors.ChainA.Sequencer.ActL2EmptyBlock(t)
}
actors.ChainB.Sequencer.ActL2EmptyBlock(t)

if (i+1)%blocksPerBatch == 0 {
system.SubmitBatchesAndAdvanceL1(t, l1BlockTimeSeconds)
}
}

// Flush any remaining unbatched blocks.
system.SubmitBatches(t, l1BlockTimeSeconds)
system.ProcessCrossSafe()

// Ensure the supervisor has processed up to the current L1 head.
l1Head := eth.InfoToL1BlockRef(eth.HeaderBlockInfo(actors.L1Miner.L1Chain().CurrentBlock()))
supStatus, err := actors.Supervisor.SyncStatus(t.Ctx())
require.NoError(t, err)
require.Equal(t, l1Head, supStatus.MinSyncedL1, "supervisor did not catch up to L1 head")

// Now execute the old message on chain B
// During consolidation, the FPP will need to verify the canonical hash of the early block
// This is where EIP-2935 should provide O(distance/8191) efficiency instead of O(distance)

actors.ChainA.Sequencer.ActL2StartBlock(t)
actors.ChainB.Sequencer.ActL2StartBlock(t)
msg.ExecuteOn(actors.ChainB)
actors.ChainA.Sequencer.ActL2EndBlock(t)
actors.ChainB.Sequencer.ActL2EndBlock(t)
system.SubmitBatchesAndAdvanceL1(t, l1BlockTimeSeconds)

endTimestamp := actors.ChainB.Sequencer.L2Unsafe().Time
startTimestamp := endTimestamp - 1
// Capture the optimistic state before cross-safe processing
preConsolidation := system.Outputs.TransitionState(startTimestamp, consolidateStep,
system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp),
system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp),
).Marshal()

// Process cross-safe to get the canonical end state
// This will verify the exec message and make the block cross-safe
system.ProcessCrossSafe()
msg.CheckExecuted()

// Verify the exec block is now cross-safe (which implies cross-unsafe) before running FPP
chainBStatus := actors.ChainB.Sequencer.SyncStatus()
require.Equal(gt, chainBStatus.SafeL2.Number, chainBStatus.UnsafeL2.Number,
"Exec block should be cross-safe before running FPP")
crossSafeEnd := system.Outputs.SuperRoot(endTimestamp)

// Run the FPP test to verify the consolidation step with RPC tracking
// The FPP will need to verify the canonical hash of the early message block
// EIP-2935 should make this efficient by using the history storage contract
test := &transitionTest{
name: "Consolidate-DeepCanonicalQuery",
agreedClaim: preConsolidation,
disputedClaim: crossSafeEnd.Marshal(),
disputedTraceIndex: consolidateStep,
expectValid: true,
}
rpcTracker := fpHelpers.NewL2RPCTracker()
rpcTracker.ForceProxyMode()
runFppTestWithL2RPCTracking(gt, test, system.Actors, system.DepSet(), rpcTracker)

// Verify EIP-2935 provides sublinear efficiency
// Without EIP-2935: would need O(N) queries for ~9900 block distance
// With EIP-2935: should need O(N/8191) queries - approximately 2-3 queries
blockDistance := totalBlocks - int(emitBlockNum)
maxExpectedLookups := blockDistance/params.HistoryServeWindow + 5 // +5 for buffer
snap := rpcTracker.Snapshot()
uniqueBlockFetches := rpcTracker.UniqueBlockFetches()
gt.Logf("Block distance: %d, unique L2 block fetches: %d, max expected: %d, rpc totals: %+v",
blockDistance, uniqueBlockFetches, maxExpectedLookups, snap.TotalByMethod)
require.Greater(gt, uniqueBlockFetches, 0, "Should have fetched at least one L2 block during consolidation")
require.LessOrEqual(gt, uniqueBlockFetches, maxExpectedLookups,
"EIP-2935 should provide sublinear efficiency, but observed too many L2 block fetches")
}

// Returns true if all tests passed, otherwise returns false
func runFppAndChallengerTests(gt *testing.T, system *dsl.InteropDSL, tests []*transitionTest) bool {
passed := true
Expand All @@ -1415,6 +1518,10 @@ func runFppAndChallengerTests(gt *testing.T, system *dsl.InteropDSL, tests []*tr
}

func runFppTest(gt *testing.T, test *transitionTest, actors *dsl.InteropActors, depSet *depset.StaticConfigDependencySet) {
runFppTestWithL2RPCTracking(gt, test, actors, depSet, nil)
}

func runFppTestWithL2RPCTracking(gt *testing.T, test *transitionTest, actors *dsl.InteropActors, depSet *depset.StaticConfigDependencySet, tracker *fpHelpers.L2RPCTracker) {
t := helpers.SubTest(gt)
if test.skipProgram {
t.Skip("Not yet implemented")
Expand All @@ -1433,13 +1540,19 @@ func runFppTest(gt *testing.T, test *transitionTest, actors *dsl.InteropActors,
if proposalTimestamp == 0 {
proposalTimestamp = actors.ChainA.Sequencer.L2Unsafe().Time
}
params := []fpHelpers.FixtureInputParam{
WithInteropEnabled(t, actors, depSet, test.agreedClaim, crypto.Keccak256Hash(test.disputedClaim), proposalTimestamp),
fpHelpers.WithL1Head(l1Head),
}
if tracker != nil {
params = append(params, fpHelpers.WithL2RPCTracker(tracker))
}
fpHelpers.RunFaultProofProgram(
t,
logger,
actors.L1Miner,
checkResult,
WithInteropEnabled(t, actors, depSet, test.agreedClaim, crypto.Keccak256Hash(test.disputedClaim), proposalTimestamp),
fpHelpers.WithL1Head(l1Head),
params...,
)
}

Expand Down
7 changes: 7 additions & 0 deletions op-e2e/actions/proofs/helpers/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ func WithL1Head(head common.Hash) FixtureInputParam {
}
}

// WithL2RPCTracker sets the L2RPCTracker to observe L2 JSON-RPC calls made by the program host.
func WithL2RPCTracker(tracker *L2RPCTracker) FixtureInputParam {
return func(f *FixtureInputs) {
f.L2RPCTracker = tracker
}
}

// RunFaultProofProgram runs the fault proof program for each state transition from genesis up to the provided l2 block num.
func (env *L2FaultProofEnv) RunFaultProofProgramFromGenesis(t helpers.Testing, finalL2BlockNum uint64, checkResult CheckResult, fixtureInputParams ...FixtureInputParam) {
l2ClaimBlockNum := uint64(0)
Expand Down
4 changes: 4 additions & 0 deletions op-e2e/actions/proofs/helpers/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ type FixtureInputs struct {
InteropEnabled bool `toml:"use-interop"`

L2Sources []*FaultProofProgramL2Source

// L2RPCTracker is an optional observer for L2 JSON-RPC calls made by the host.
// It is not serialized as part of the test fixture inputs.
L2RPCTracker *L2RPCTracker `toml:"-"`
}
Loading