diff --git a/op-acceptance-tests/tests/depreqres/common/common.go b/op-acceptance-tests/tests/depreqres/common/common.go new file mode 100644 index 00000000000..7dd6cfea954 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/common/common.go @@ -0,0 +1,103 @@ +package common + +import ( + "testing" + "time" + + "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-node/rollup/sync" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func UnsafeChainNotStalling_Disconnect(gt *testing.T, syncMode sync.Mode, sleep time.Duration) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainMultiNodeWithoutCheck(t) + require := t.Require() + l := t.Logger().With("syncmode", syncMode) + + l.Info("Confirm that the CL nodes are progressing the unsafe chain") + target := uint64(3) + dsl.CheckAll(t, + sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30), + sys.L2CLB.AdvancedFn(types.LocalUnsafe, target, 30), + ) + + l.Info("Disconnect L2CL from L2CLB, and vice versa") + sys.L2CLB.DisconnectPeer(sys.L2CL) + sys.L2CL.DisconnectPeer(sys.L2CLB) + + ssA_before := sys.L2CL.SyncStatus() + ssB_before := sys.L2CLB.SyncStatus() + + l.Info("L2CL status before delay", "unsafeL2", ssA_before.UnsafeL2.ID(), "safeL2", ssA_before.SafeL2.ID()) + l.Info("L2CLB status before delay", "unsafeL2", ssB_before.UnsafeL2.ID(), "safeL2", ssB_before.SafeL2.ID()) + + time.Sleep(sleep) + + ssA_after := sys.L2CL.SyncStatus() + ssB_after := sys.L2CLB.SyncStatus() + + l.Info("L2CL status after delay", "unsafeL2", ssA_after.UnsafeL2.ID(), "safeL2", ssA_after.SafeL2.ID()) + l.Info("L2CLB status after delay", "unsafeL2", ssB_after.UnsafeL2.ID(), "safeL2", ssB_after.SafeL2.ID()) + + require.Greater(ssA_after.UnsafeL2.Number, ssA_before.UnsafeL2.Number, "unsafe chain for L2CL should have advanced") + require.Equal(ssB_after.UnsafeL2.Number, ssB_before.UnsafeL2.Number, "unsafe chain for L2CLB should have stalled") + + l.Info("Re-connect L2CL to L2CLB") + sys.L2CLB.ConnectPeer(sys.L2CL) + sys.L2CL.ConnectPeer(sys.L2CLB) + + l.Info("Confirm that the unsafe chain for L2CLB is not stalled") + sys.L2CLB.Reached(types.LocalUnsafe, ssA_after.UnsafeL2.Number, 30) + sys.L2ELB.Reached(eth.Unsafe, ssA_after.UnsafeL2.Number, 30) +} + +func UnsafeChainNotStalling_RestartOpNode(gt *testing.T, syncMode sync.Mode, sleep time.Duration) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainMultiNodeWithoutCheck(t) + require := t.Require() + l := t.Logger().With("syncmode", syncMode) + + l.Info("Confirm that the CL nodes are progressing the unsafe chain") + target := uint64(3) + dsl.CheckAll(t, + sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30), + sys.L2CLB.AdvancedFn(types.LocalUnsafe, target, 30), + ) + + l.Info("Disconnect L2CL from L2CLB, and vice versa") + sys.L2CLB.DisconnectPeer(sys.L2CL) + sys.L2CL.DisconnectPeer(sys.L2CLB) + + ssA_before := sys.L2CL.SyncStatus() + ssB_before := sys.L2CLB.SyncStatus() + + l.Info("L2CL status before delay", "unsafeL2", ssA_before.UnsafeL2.ID(), "safeL2", ssA_before.SafeL2.ID()) + l.Info("L2CLB status before delay", "unsafeL2", ssB_before.UnsafeL2.ID(), "safeL2", ssB_before.SafeL2.ID()) + + sys.L2CLB.Stop() + + time.Sleep(sleep) + + sys.L2CLB.Start() + + ssA_after := sys.L2CL.SyncStatus() + ssB_after := sys.L2CLB.SyncStatus() + + l.Info("L2CL status after delay", "unsafeL2", ssA_after.UnsafeL2.ID(), "safeL2", ssA_after.SafeL2.ID()) + l.Info("L2CLB status after delay", "unsafeL2", ssB_after.UnsafeL2.ID(), "safeL2", ssB_after.SafeL2.ID()) + + require.Greater(ssA_after.UnsafeL2.Number, ssA_before.UnsafeL2.Number, "unsafe chain for L2CL should have advanced") + require.LessOrEqual(ssB_after.UnsafeL2.Number, ssB_before.UnsafeL2.Number, "unsafe chain for L2CLB should have stalled") + + l.Info("Re-connect L2CL to L2CLB") + sys.L2CLB.ConnectPeer(sys.L2CL) + sys.L2CL.ConnectPeer(sys.L2CLB) + + l.Info("Confirm that the unsafe chain for L2CLB is not stalled") + sys.L2CLB.Reached(types.LocalUnsafe, ssA_after.UnsafeL2.Number, 30) + sys.L2ELB.Reached(eth.Unsafe, ssA_after.UnsafeL2.Number, 30) +} diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/clsync/clsync_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/clsync/clsync_test.go new file mode 100644 index 00000000000..1080f90f4b2 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/clsync/clsync_test.go @@ -0,0 +1,21 @@ +package clsync + +import ( + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/depreqres/common" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" +) + +func TestUnsafeChainNotStalling_CLSync_Short(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.CLSync, 20*time.Second) +} + +func TestUnsafeChainNotStalling_CLSync_Long(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.CLSync, 95*time.Second) +} + +func TestUnsafeChainNotStalling_CLSync_RestartOpNode_Long(gt *testing.T) { + common.UnsafeChainNotStalling_RestartOpNode(gt, sync.CLSync, 95*time.Second) +} diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/clsync/init_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/clsync/init_test.go new file mode 100644 index 00000000000..6163f1cc02e --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/clsync/init_test.go @@ -0,0 +1,23 @@ +package clsync + +import ( + "testing" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func TestMain(m *testing.M) { + presets.DoMain(m, presets.WithSingleChainMultiNode(), + presets.WithConsensusLayerSync(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithReqRespSyncDisabled(), + presets.WithNoDiscovery(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + })), + ) +} diff --git a/op-acceptance-tests/tests/depreqres/depreqres_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go similarity index 75% rename from op-acceptance-tests/tests/depreqres/depreqres_test.go rename to op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go index 79fa05817e0..a887864e720 100644 --- a/op-acceptance-tests/tests/depreqres/depreqres_test.go +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go @@ -7,25 +7,23 @@ import ( "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-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) -func TestUnsafeChainStalling_DisabledReqRespSync(gt *testing.T) { +func TestUnsafeChainNotStalling_DisabledReqRespSync(gt *testing.T) { t := devtest.SerialT(gt) - sys := presets.NewSingleChainMultiNode(t) + sys := presets.NewSingleChainMultiNodeWithoutCheck(t) require := t.Require() l := t.Logger() l.Info("Confirm that the CL nodes are progressing the unsafe chain") - target := uint64(10) + delta := uint64(3) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30), - sys.L2CLB.AdvancedFn(types.LocalUnsafe, target, 30), + sys.L2CL.AdvancedFn(types.LocalUnsafe, delta, 30), + sys.L2CLB.AdvancedFn(types.LocalUnsafe, delta, 30), ) - l.Info("Stop the L2 batcher") - sys.L2Batcher.Stop() - l.Info("Disconnect L2CL from L2CLB, and vice versa") sys.L2CLB.DisconnectPeer(sys.L2CL) sys.L2CL.DisconnectPeer(sys.L2CLB) @@ -51,6 +49,9 @@ func TestUnsafeChainStalling_DisabledReqRespSync(gt *testing.T) { sys.L2CLB.ConnectPeer(sys.L2CL) sys.L2CL.ConnectPeer(sys.L2CLB) - l.Info("Confirm that the unsafe chain for L2CLB is stalled") - sys.L2CLB.NotAdvanced(types.LocalUnsafe, 10) + l.Info("Confirm that the unsafe chain for L2CLB can advance") + dsl.CheckAll(t, + sys.L2CLB.AdvancedFn(types.LocalUnsafe, delta, 30), + sys.L2ELB.AdvancedFn(eth.Unsafe, delta), + ) } diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go new file mode 100644 index 00000000000..ba866b76383 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go @@ -0,0 +1,70 @@ +package divergence + +import ( + "testing" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum" +) + +func TestMain(m *testing.M) { + // No ELP2P, CLP2P to control the supply of unsafe payload to the CL + presets.DoMain(m, presets.WithSingleChainMultiNodeWithoutP2P(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithExecutionLayerSyncOnVerifiers(), + presets.WithReqRespSyncDisabled(), + presets.WithNoDiscovery(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + })), + ) +} + +// TestCLELDivergence tests that the CL and EL diverge when the CL advances the unsafe head, due to accepting SYNCING response from the EL, but the EL cannot validate the block (yet), does not canonicalize it, and doesn't serve it. +func TestCLELDivergence(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainMultiNodeWithoutCheck(t) + require := t.Require() + l := t.Logger() + + sys.L2CL.Advanced(types.LocalUnsafe, 8, 30) + + // batcher down so safe not advanced + require.Equal(uint64(0), sys.L2CL.HeadBlockRef(types.LocalSafe).Number) + require.Equal(uint64(0), sys.L2CLB.HeadBlockRef(types.LocalSafe).Number) + + startNum := sys.L2CLB.HeadBlockRef(types.LocalUnsafe).Number + + // Finish EL sync by supplying the first block + // EL Sync finished because underlying EL has states to validate the payload for block startNum+1 + sys.L2CLB.SignalTarget(sys.L2EL, startNum+1) + require.Equal(startNum+1, sys.L2ELB.BlockRefByLabel(eth.Unsafe).Number) + + for _, delta := range []uint64{3, 4, 5} { + targetNumber := startNum + delta + l.Info("Sending payload ", "target", targetNumber, "startNum", startNum) + sys.L2CLB.SignalTarget(sys.L2EL, targetNumber) + + // Canonical unsafe head never advances because of the gap + require.Equal(startNum+1, sys.L2ELB.BlockRefByLabel(eth.Unsafe).Number) + + // Unsafe head on CL advanced, but on EL we cannot fetch state for the unsafe block hash yet + targetBlock := sys.L2EL.BlockRefByNumber(targetNumber) + + // Confirm that L2CLB SyncStatus returns the newest unsafe block number and hash + ss := sys.L2CLB.SyncStatus() + require.Equal(targetNumber, ss.UnsafeL2.Number) + require.Equal(targetBlock.Hash, ss.UnsafeL2.Hash) + + // Confirm that L2ELB cannot fetch the block by hash yet, because the block is not canonicalized, even though the CL reference is set to it. + _, err := sys.L2ELB.Escape().L2EthClient().L2BlockRefByHash(t.Ctx(), ss.UnsafeL2.Hash) + require.Error(err, ethereum.NotFound) + } +} diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/elsync/elsync_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/elsync/elsync_test.go new file mode 100644 index 00000000000..09bdb325de5 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/elsync/elsync_test.go @@ -0,0 +1,21 @@ +package elsync + +import ( + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/depreqres/common" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" +) + +func TestUnsafeChainNotStalling_ELSync_Short(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.ELSync, 20*time.Second) +} + +func TestUnsafeChainNotStalling_ELSync_Long(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.ELSync, 95*time.Second) +} + +func TestUnsafeChainNotStalling_ELSync_RestartOpNode_Long(gt *testing.T) { + common.UnsafeChainNotStalling_RestartOpNode(gt, sync.ELSync, 95*time.Second) +} diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/elsync/init_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/elsync/init_test.go new file mode 100644 index 00000000000..867ffcec2c5 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/elsync/init_test.go @@ -0,0 +1,23 @@ +package elsync + +import ( + "testing" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func TestMain(m *testing.M) { + presets.DoMain(m, presets.WithSingleChainMultiNode(), + presets.WithExecutionLayerSyncOnVerifiers(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithReqRespSyncDisabled(), + presets.WithNoDiscovery(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + })), + ) +} diff --git a/op-acceptance-tests/tests/depreqres/init_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/init_test.go similarity index 57% rename from op-acceptance-tests/tests/depreqres/init_test.go rename to op-acceptance-tests/tests/depreqres/reqressyncdisabled/init_test.go index 373e0a49778..10fe7cec718 100644 --- a/op-acceptance-tests/tests/depreqres/init_test.go +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/init_test.go @@ -3,8 +3,11 @@ package depreqres import ( "testing" + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-devstack/compat" "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" ) func TestMain(m *testing.M) { @@ -13,5 +16,8 @@ func TestMain(m *testing.M) { presets.WithCompatibleTypes(compat.SysGo), presets.WithReqRespSyncDisabled(), presets.WithNoDiscovery(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + })), ) } diff --git a/op-acceptance-tests/tests/depreqres/syncmodereqressync/clsync/clsync_test.go b/op-acceptance-tests/tests/depreqres/syncmodereqressync/clsync/clsync_test.go new file mode 100644 index 00000000000..1080f90f4b2 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/syncmodereqressync/clsync/clsync_test.go @@ -0,0 +1,21 @@ +package clsync + +import ( + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/depreqres/common" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" +) + +func TestUnsafeChainNotStalling_CLSync_Short(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.CLSync, 20*time.Second) +} + +func TestUnsafeChainNotStalling_CLSync_Long(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.CLSync, 95*time.Second) +} + +func TestUnsafeChainNotStalling_CLSync_RestartOpNode_Long(gt *testing.T) { + common.UnsafeChainNotStalling_RestartOpNode(gt, sync.CLSync, 95*time.Second) +} diff --git a/op-acceptance-tests/tests/depreqres/syncmodereqressync/clsync/init_test.go b/op-acceptance-tests/tests/depreqres/syncmodereqressync/clsync/init_test.go new file mode 100644 index 00000000000..51dd93bfa3b --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/syncmodereqressync/clsync/init_test.go @@ -0,0 +1,23 @@ +package clsync + +import ( + "testing" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func TestMain(m *testing.M) { + presets.DoMain(m, presets.WithSingleChainMultiNode(), + presets.WithConsensusLayerSync(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithSyncModeReqRespSync(), + presets.WithNoDiscovery(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + })), + ) +} diff --git a/op-acceptance-tests/tests/depreqres/syncmodereqressync/elsync/elsync_test.go b/op-acceptance-tests/tests/depreqres/syncmodereqressync/elsync/elsync_test.go new file mode 100644 index 00000000000..09bdb325de5 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/syncmodereqressync/elsync/elsync_test.go @@ -0,0 +1,21 @@ +package elsync + +import ( + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/depreqres/common" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" +) + +func TestUnsafeChainNotStalling_ELSync_Short(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.ELSync, 20*time.Second) +} + +func TestUnsafeChainNotStalling_ELSync_Long(gt *testing.T) { + common.UnsafeChainNotStalling_Disconnect(gt, sync.ELSync, 95*time.Second) +} + +func TestUnsafeChainNotStalling_ELSync_RestartOpNode_Long(gt *testing.T) { + common.UnsafeChainNotStalling_RestartOpNode(gt, sync.ELSync, 95*time.Second) +} diff --git a/op-acceptance-tests/tests/depreqres/syncmodereqressync/elsync/init_test.go b/op-acceptance-tests/tests/depreqres/syncmodereqressync/elsync/init_test.go new file mode 100644 index 00000000000..311ed9f8a33 --- /dev/null +++ b/op-acceptance-tests/tests/depreqres/syncmodereqressync/elsync/init_test.go @@ -0,0 +1,23 @@ +package elsync + +import ( + "testing" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func TestMain(m *testing.M) { + presets.DoMain(m, presets.WithSingleChainMultiNode(), + presets.WithExecutionLayerSyncOnVerifiers(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithSyncModeReqRespSync(), + presets.WithNoDiscovery(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + cfg.Stopped = true + })), + ) +} diff --git a/op-acceptance-tests/tests/sync/clsync/gap_clp2p/init_test.go b/op-acceptance-tests/tests/sync/clsync/gap_clp2p/init_test.go new file mode 100644 index 00000000000..5da44bdcd94 --- /dev/null +++ b/op-acceptance-tests/tests/sync/clsync/gap_clp2p/init_test.go @@ -0,0 +1,22 @@ +package gap_clp2p + +import ( + "testing" + + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func TestMain(m *testing.M) { + // No ELP2P, CLP2P to control the supply of unsafe payload to the CL + presets.DoMain(m, presets.WithSingleChainMultiNodeWithoutP2P(), + presets.WithCompatibleTypes(compat.SysGo), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + // For stopping derivation, not to advance safe heads + cfg.Stopped = true + })), + ) +} diff --git a/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go b/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go new file mode 100644 index 00000000000..f4cc41ac27d --- /dev/null +++ b/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go @@ -0,0 +1,53 @@ +package gap_clp2p + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +// TestSyncAfterInitialELSync tests that blocks received out of order would be processed in order when running in CL sync mode. Note that this is not going to happen when running in EL sync mode, which relies on healthy ELP2P, something that is disabled in this test. +func TestSyncAfterInitialELSync(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainMultiNodeWithoutCheck(t) + require := t.Require() + + sys.L2CL.Advanced(types.LocalUnsafe, 7, 30) + + // batcher down so safe not advanced + require.Zero(sys.L2CL.HeadBlockRef(types.LocalSafe).Number) + require.Zero(sys.L2CLB.HeadBlockRef(types.LocalSafe).Number) + + startNum := sys.L2CLB.HeadBlockRef(types.LocalUnsafe).Number + + // Finish EL sync by supplying the first block + // EL Sync finished because underlying EL has states to validate the payload for block startNum+1 + sys.L2CLB.SignalTarget(sys.L2EL, startNum+1) + + // Send payloads for block startNum+3, startNum+4, startNum+5, startNum+7 which will fill in unsafe payload queue, block startNum+2, and block startNum+6 missed + // Non-canonical payloads will be not sent to L2EL + // Order does not matter + for _, delta := range []uint64{5, 3, 4, 7} { + target := startNum + delta + sys.L2CLB.SignalTarget(sys.L2EL, target) + // Canonical unsafe head never advances because of the gap + require.Equal(startNum+1, sys.L2ELB.BlockRefByLabel(eth.Unsafe).Number) + } + + // Send missing gap, payload startNum+2, still not sending FCU since unsafe gap exists + sys.L2CLB.SignalTarget(sys.L2EL, startNum+2) + + retries := 2 + // Gap filled and payload startNum+2, startNum+3, startNum+4, startNum+5 became canonical by relaying to ELB. + // Payload startNum+7 is still in the unsafe payload queue because of unsafe gap + sys.L2ELB.Reached(eth.Unsafe, startNum+5, retries) + + // Send missing gap, payload startNum+6 + sys.L2CLB.SignalTarget(sys.L2EL, startNum+6) + + // Gap filled and block startNum+6, startNum+7 became canonical by relaying to ELB. + sys.L2ELB.Reached(eth.Unsafe, startNum+7, retries) +} diff --git a/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go b/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go index 8541113d1ac..f79d9ab8c94 100644 --- a/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go +++ b/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go @@ -10,48 +10,6 @@ import ( "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) -func TestSyncAfterInitialELSync(gt *testing.T) { - t := devtest.SerialT(gt) - sys := presets.NewSingleChainMultiNodeWithoutCheck(t) - require := t.Require() - - sys.L2CL.Advanced(types.LocalUnsafe, 7, 30) - - // batcher down so safe not advanced - require.Equal(uint64(0), sys.L2CL.HeadBlockRef(types.LocalSafe).Number) - require.Equal(uint64(0), sys.L2CLB.HeadBlockRef(types.LocalSafe).Number) - - startNum := sys.L2CLB.HeadBlockRef(types.LocalUnsafe).Number - - // Finish EL sync by supplying the first block - // EL Sync finished because underlying EL has states to validate the payload for block startNum+1 - sys.L2CLB.SignalTarget(sys.L2EL, startNum+1) - - // Send payloads for block startNum+3, startNum+4, startNum+5, startNum+7 which will fill in unsafe payload queue, block startNum+2 missed - // Non-canonical payloads will be not sent to L2EL - // Order does not matter - for _, delta := range []uint64{5, 3, 4, 7} { - target := startNum + delta - sys.L2CLB.SignalTarget(sys.L2EL, target) - // Canonical unsafe head never advances because of the gap - require.Equal(startNum+1, sys.L2ELB.BlockRefByLabel(eth.Unsafe).Number) - } - - // Send missing gap, payload startNum+2, still not sending FCU since unsafe gap exists - sys.L2CLB.SignalTarget(sys.L2EL, startNum+2) - - retries := 2 - // Gap filled and payload startNum+2, startNum+3, startNum+4, startNum+5 became canonical by relaying to ELB. - // Payload startNum+7 is still in the unsafe payload queue because of unsafe gap - sys.L2ELB.Reached(eth.Unsafe, startNum+5, retries) - - // Send missing gap, payload startNum+6 - sys.L2CLB.SignalTarget(sys.L2EL, startNum+6) - - // Gap filled and block startNum+6, startNum+7 became canonical by relaying to ELB. - sys.L2ELB.Reached(eth.Unsafe, startNum+7, retries) -} - func TestReachUnsafeTipByAppendingUnsafePayload(gt *testing.T) { t := devtest.SerialT(gt) sys := presets.NewSingleChainMultiNodeWithoutCheck(t) diff --git a/op-devstack/dsl/l2_el.go b/op-devstack/dsl/l2_el.go index ef66f6958df..30a920de046 100644 --- a/op-devstack/dsl/l2_el.go +++ b/op-devstack/dsl/l2_el.go @@ -113,7 +113,7 @@ func (el *L2ELNode) ReachedFn(label eth.BlockLabel, target uint64, attempts int) return nil } logger.Info("L2EL sync status", "current", head.Number) - return fmt.Errorf("expected head to advance: %s", label) + return fmt.Errorf("expected head for label=%s to advance to target=%d, but got current=%d", label, target, head.Number) }) } } diff --git a/op-devstack/presets/cl_config.go b/op-devstack/presets/cl_config.go index 1a425dfc8de..3760cb3ab9a 100644 --- a/op-devstack/presets/cl_config.go +++ b/op-devstack/presets/cl_config.go @@ -37,6 +37,15 @@ func WithReqRespSyncDisabled() stack.CommonOption { sysgo.WithGlobalL2CLOption(sysgo.L2CLOptionFn( func(_ devtest.P, id stack.L2CLNodeID, cfg *sysgo.L2CLConfig) { cfg.EnableReqRespSync = false + cfg.UseReqRespSync = false + }))) +} + +func WithSyncModeReqRespSync() stack.CommonOption { + return stack.MakeCommon( + sysgo.WithGlobalL2CLOption(sysgo.L2CLOptionFn( + func(_ devtest.P, id stack.L2CLNodeID, cfg *sysgo.L2CLConfig) { + cfg.UseReqRespSync = true }))) } diff --git a/op-devstack/sysgo/l2_cl.go b/op-devstack/sysgo/l2_cl.go index 8e69113d8eb..3e1b4a4e658 100644 --- a/op-devstack/sysgo/l2_cl.go +++ b/op-devstack/sysgo/l2_cl.go @@ -31,6 +31,9 @@ type L2CLConfig struct { // EnableReqRespSync is the flag to enable/disable req-resp sync. EnableReqRespSync bool + // UseReqRespSync controls whether to use the req-resp sync protocol. EnableReqRespSync == false && UseReqRespSync == true is not allowed, and node will fail to start. + UseReqRespSync bool + // NoDiscovery is the flag to enable/disable discovery NoDiscovery bool } @@ -55,6 +58,7 @@ func DefaultL2CLConfig() *L2CLConfig { IsSequencer: false, IndexingMode: false, EnableReqRespSync: true, + UseReqRespSync: true, NoDiscovery: false, } } diff --git a/op-devstack/sysgo/l2_cl_opnode.go b/op-devstack/sysgo/l2_cl_opnode.go index dbcf1465cd5..6f867b20313 100644 --- a/op-devstack/sysgo/l2_cl_opnode.go +++ b/op-devstack/sysgo/l2_cl_opnode.go @@ -288,6 +288,7 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L Tracer: nil, Sync: nodeSync.Config{ SyncMode: syncMode, + SyncModeReqResp: cfg.UseReqRespSync, SkipSyncStartCheck: false, SupportsPostFinalizationELSync: false, }, diff --git a/op-e2e/system/p2p/reqresp_test.go b/op-e2e/system/p2p/reqresp_test.go index bb9b195a51e..2680f9fb910 100644 --- a/op-e2e/system/p2p/reqresp_test.go +++ b/op-e2e/system/p2p/reqresp_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/interop" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/endpoint" "github.com/ethereum-optimism/optimism/op-service/eth" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" @@ -52,6 +53,9 @@ func TestSystemP2PAltSync(t *testing.T) { }, InteropConfig: &interop.Config{}, L1EpochPollInterval: time.Second * 4, + Sync: sync.Config{ + SyncModeReqResp: true, + }, } cfg.Nodes["bob"] = &config.Config{ Driver: driver.Config{ @@ -61,6 +65,9 @@ func TestSystemP2PAltSync(t *testing.T) { }, InteropConfig: &interop.Config{}, L1EpochPollInterval: time.Second * 4, + Sync: sync.Config{ + SyncModeReqResp: true, + }, } cfg.Loggers["alice"] = testlog.Logger(t, log.LevelInfo).New("role", "alice") cfg.Loggers["bob"] = testlog.Logger(t, log.LevelInfo).New("role", "bob") @@ -136,6 +143,9 @@ func TestSystemP2PAltSync(t *testing.T) { syncedPayloads = append(syncedPayloads, payload.ExecutionPayload.ID().String()) }, }, + Sync: sync.Config{ + SyncModeReqResp: true, + }, } e2esys.ConfigureL1(syncNodeCfg, sys.EthInstances["l1"], sys.L1BeaconEndpoint()) syncerL2Engine, err := geth.InitL2("syncer", sys.L2GenesisCfg, cfg.JWTFilePath) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 90326aeb8d8..1291a32638d 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -15,6 +15,7 @@ import ( oplog "github.com/ethereum-optimism/optimism/op-service/log" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" + "github.com/ethereum-optimism/optimism/op-service/ptr" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/sources" ) @@ -109,13 +110,17 @@ var ( Category: L1RPCCategory, } SyncModeFlag = &cli.GenericFlag{ - Name: "syncmode", - Usage: fmt.Sprintf("Blockchain sync mode (options: %s)", openum.EnumString(sync.ModeStrings)), - EnvVars: prefixEnvVars("SYNCMODE"), - Value: func() *sync.Mode { - out := sync.CLSync - return &out - }(), + Name: "syncmode", + Usage: fmt.Sprintf("Blockchain sync mode (options: %s)", openum.EnumString(sync.ModeStrings)), + EnvVars: prefixEnvVars("SYNCMODE"), + Value: ptr.New(sync.CLSync), + Category: RollupCategory, + } + SyncModeReqRespFlag = &cli.BoolFlag{ + Name: "syncmode.req-resp", + Required: false, + Value: true, + EnvVars: prefixEnvVars("SYNCMODE_REQ_RESP"), Category: RollupCategory, } RPCAdminPersistence = &cli.StringFlag{ @@ -448,6 +453,7 @@ var optionalFlags = []cli.Flag{ BeaconCheckIgnore, BeaconFetchAllSidecars, SyncModeFlag, + SyncModeReqRespFlag, FetchWithdrawalRootFromState, L1TrustRPC, L1RPCProviderKind, diff --git a/op-node/flags/p2p_flags.go b/op-node/flags/p2p_flags.go index e6e51a84e91..3ed9cb91459 100644 --- a/op-node/flags/p2p_flags.go +++ b/op-node/flags/p2p_flags.go @@ -397,7 +397,7 @@ func P2PFlags(envPrefix string) []cli.Flag { }, &cli.BoolFlag{ Name: SyncReqRespName, - Usage: "Enables P2P req-resp alternative sync method, on both server and client side.", + Usage: "Enables P2P req-resp sync server and client.", Value: true, Required: false, EnvVars: p2pEnv(envPrefix, "SYNC_REQ_RESP"), diff --git a/op-node/rollup/attributes/attributes.go b/op-node/rollup/attributes/attributes.go index 15d75e7d8d0..fef5f5ddb38 100644 --- a/op-node/rollup/attributes/attributes.go +++ b/op-node/rollup/attributes/attributes.go @@ -187,6 +187,7 @@ func (eq *AttributesHandler) onPendingSafeUpdate(ctx context.Context, x engine.P } else { // append to tip otherwise eq.sentAttributes = true + eq.log.Debug("emitting build start event", "attributes", eq.attributes) eq.emitter.Emit(ctx, engine.BuildStartEvent{Attributes: eq.attributes}) } } diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index 0c22d211b38..44a6c4e3227 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -136,6 +136,7 @@ func NewDriver( stateReq: make(chan chan struct{}), forceReset: make(chan chan struct{}, 10), driverConfig: driverCfg, + syncConfig: syncCfg, driverCtx: driverCtx, driverCancel: driverCancel, log: log, @@ -169,6 +170,8 @@ type Driver struct { // May not be modified after starting the Driver. driverConfig *Config + syncConfig *sync.Config + // Interface to signal the L2 block range to sync. altSync AltSync @@ -278,6 +281,7 @@ func (s *Driver) eventLoop() { // If the engine is not ready, or if the L2 head is actively changing, then reset the alt-sync: // there is no need to request L2 blocks when we are syncing already. if head := s.SyncDeriver.Engine.UnsafeL2Head(); head != lastUnsafeL2 || !s.SyncDeriver.Derivation.DerivationReady() { + s.log.Debug("altSyncTicker reset", "head", head, "lastUnsafeL2", lastUnsafeL2, "derivationReady", s.SyncDeriver.Derivation.DerivationReady()) lastUnsafeL2 = head altSyncTicker.Reset(syncCheckInterval) } @@ -390,20 +394,33 @@ func (s *Driver) BlockRefWithStatus(ctx context.Context, num uint64) (eth.L2Bloc } } -// checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads from an alt-sync method. -// WARNING: This is only an outgoing signal, the blocks are not guaranteed to be retrieved. -// Results are received through OnUnsafeL2Payload. +// checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads func (s *Driver) checkForGapInUnsafeQueue(ctx context.Context) error { start := s.SyncDeriver.Engine.UnsafeL2Head() - end := s.SyncDeriver.Engine.LowestQueuedUnsafeBlock() - // Check if we have missing blocks between the start and end. Request them if we do. - if end == (eth.L2BlockRef{}) { - s.log.Debug("requesting sync with open-end range", "start", start) - return s.altSync.RequestL2Range(ctx, start, eth.L2BlockRef{}) - } else if end.Number > start.Number+1 { - s.log.Debug("requesting missing unsafe L2 block range", "start", start, "end", end, "size", end.Number-start.Number) - return s.altSync.RequestL2Range(ctx, start, end) + payload, end := s.SyncDeriver.Engine.PeekUnsafePayload() + + if s.syncConfig.SyncModeReqResp { + if end == (eth.L2BlockRef{}) { + s.log.Debug("requesting rrsync with open-end range", "start", start) + return s.altSync.RequestL2Range(ctx, start, eth.L2BlockRef{}) + } else if end.Number > start.Number+1 { + s.log.Debug("requesting rrsync missing unsafe L2 block range", "start", start, "end", end, "size", end.Number-start.Number) + return s.altSync.RequestL2Range(ctx, start, end) + } + } else { + if end == (eth.L2BlockRef{}) { + s.log.Debug("checkForGapInUnsafeQueue: no unsafe payload in queue", "start", start) + return nil + } else if end.Number > start.Number+1 { + s.log.Info("requesting engine missing unsafe L2 block range", "start", start, "end", end, "size", end.Number-start.Number) + err := s.SyncDeriver.Engine.InsertUnsafePayload(ctx, payload, end) + if err != nil { + s.log.Error("failed to insert unsafe payload", "err", err) + } + return err + } } + return nil } diff --git a/op-node/rollup/driver/sync_deriver.go b/op-node/rollup/driver/sync_deriver.go index 248844e9622..a2b1561bfd1 100644 --- a/op-node/rollup/driver/sync_deriver.go +++ b/op-node/rollup/driver/sync_deriver.go @@ -104,24 +104,29 @@ func (s *SyncDeriver) OnEvent(ctx context.Context, ev event.Event) bool { } func (s *SyncDeriver) OnUnsafeL2Payload(ctx context.Context, envelope *eth.ExecutionPayloadEnvelope) { - // If we are doing CL sync or done with engine syncing, fallback to the unsafe payload queue & CL P2P sync. - if s.SyncCfg.SyncMode == sync.CLSync || !s.Engine.IsEngineSyncing() { - s.Log.Info("Optimistically queueing unsafe L2 execution payload", "id", envelope.ExecutionPayload.ID()) + s.Log.Debug("OnUnsafeL2Payload called", "id", envelope.ExecutionPayload.ID(), "syncMode", s.SyncCfg.SyncMode, "followUnsafeWithRRSync", s.SyncCfg.SyncModeReqResp) + + if s.SyncCfg.SyncMode == sync.CLSync || (!s.Engine.IsEngineInitialELSyncing() && s.SyncCfg.SyncModeReqResp) { s.Engine.AddUnsafePayload(ctx, envelope) - } else if s.SyncCfg.SyncMode == sync.ELSync { + return + } + + if s.SyncCfg.SyncMode == sync.ELSync { ref, err := derive.PayloadToBlockRef(s.Config, envelope.ExecutionPayload) if err != nil { - s.Log.Info("Failed to turn execution payload into a block ref", "id", envelope.ExecutionPayload.ID(), "err", err) + s.Log.Error("Failed to turn execution payload into a block ref", "id", envelope.ExecutionPayload.ID(), "err", err) return } if ref.Number <= s.Engine.UnsafeL2Head().Number { return } - s.Log.Info("Optimistically inserting unsafe L2 execution payload to drive EL sync", "id", envelope.ExecutionPayload.ID()) + s.Log.Info("Inserting unsafe L2 execution payload to drive EL sync", "id", envelope.ExecutionPayload.ID()) if err := s.Engine.InsertUnsafePayload(s.Ctx, envelope, ref); err != nil { s.Log.Warn("Failed to insert unsafe payload for EL sync", "id", envelope.ExecutionPayload.ID(), "err", err) } + return } + } func (s *SyncDeriver) onSafeDerivedBlock(ctx context.Context, x engine.SafeDerivedEvent) { @@ -224,7 +229,7 @@ func (s *SyncDeriver) SyncStep() { s.Engine.TryUpdateEngine(s.Ctx) - if s.Engine.IsEngineSyncing() { + if s.Engine.IsEngineInitialELSyncing() { // The pipeline cannot move forwards if doing EL sync. s.Log.Debug("Rollup driver is backing off because execution engine is syncing.", "unsafe_head", s.Engine.UnsafeL2Head()) diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index 6e3d8acac81..306c40c2cc4 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -220,13 +220,13 @@ func (e *EngineController) requestForkchoiceUpdate(ctx context.Context) { }) } -func (e *EngineController) IsEngineSyncing() bool { +func (e *EngineController) IsEngineInitialELSyncing() bool { e.mu.Lock() defer e.mu.Unlock() - return e.isEngineSyncing() + return e.isEngineInitialELSyncing() } -func (e *EngineController) isEngineSyncing() bool { +func (e *EngineController) isEngineInitialELSyncing() bool { return e.syncStatus == syncStatusWillStartEL || e.syncStatus == syncStatusStartedEL || e.syncStatus == syncStatusFinishedELButNotFinalized @@ -358,6 +358,11 @@ func (e *EngineController) checkNewPayloadStatus(status eth.ExecutePayloadStatus // Allow SYNCING and ACCEPTED if engine EL sync is enabled return status == eth.ExecutionValid || status == eth.ExecutionSyncing || status == eth.ExecutionAccepted } + // if SyncModeReqResp is false, meaning we no longer use Req/Res P2P protocol, we should also tolerate SYNCING response, when in sync.CLSync mode, so that + // the CL node can get to making an FCU call after NewPayload returns SYNCING, and can trigger the EL sync behavior. + if !e.syncCfg.SyncModeReqResp { + return status == eth.ExecutionValid || status == eth.ExecutionSyncing + } return status == eth.ExecutionValid } @@ -428,7 +433,7 @@ func (e *EngineController) tryUpdateEngineInternal(ctx context.Context) error { if !e.needFCUCall { return ErrNoFCUNeeded } - if e.isEngineSyncing() { + if e.isEngineInitialELSyncing() { e.log.Warn("Attempting to update forkchoice state while EL syncing") } if err := e.initializeUnknowns(ctx); err != nil { @@ -520,6 +525,7 @@ func (e *EngineController) insertUnsafePayload(ctx context.Context, envelope *et if err != nil { return derive.NewTemporaryError(fmt.Errorf("failed to update insert payload: %w", err)) } + e.log.Debug("insertUnsafePayload e.NewPayload returned", "ref", ref, "status", status.Status) if status.Status == eth.ExecutionInvalid { e.emitter.Emit(ctx, PayloadInvalidEvent{ Envelope: envelope, @@ -566,6 +572,7 @@ func (e *EngineController) insertUnsafePayload(ctx context.Context, envelope *et return derive.NewTemporaryError(fmt.Errorf("failed to update forkchoice to prepare for new unsafe payload: %w", err)) } } + e.log.Debug("insertUnsafePayload e.ForkchoiceUpdate returned", "ref", ref, "status", fcRes.PayloadStatus.Status) if !e.checkForkchoiceUpdatedStatus(fcRes.PayloadStatus.Status) { payload := envelope.ExecutionPayload return derive.NewTemporaryError(fmt.Errorf("cannot prepare unsafe chain for new payload: new - %v; parent: %v; err: %w", @@ -615,7 +622,7 @@ func (e *EngineController) shouldTryBackupUnsafeReorg() bool { return false } // This method must be never called when EL sync. If EL sync is in progress, early return. - if e.isEngineSyncing() { + if e.isEngineInitialELSyncing() { e.log.Warn("Attempting to unsafe reorg using backupUnsafe while EL syncing") return false } @@ -904,17 +911,16 @@ func (e *EngineController) forceReset(ctx context.Context, localUnsafe, crossUns ) } -// LowestQueuedUnsafeBlock retrieves the first queued-up L2 unsafe payload, or a zeroed reference if there is none. -func (e *EngineController) LowestQueuedUnsafeBlock() eth.L2BlockRef { +func (e *EngineController) PeekUnsafePayload() (*eth.ExecutionPayloadEnvelope, eth.L2BlockRef) { payload := e.unsafePayloads.Peek() if payload == nil { - return eth.L2BlockRef{} + return nil, eth.L2BlockRef{} } ref, err := derive.PayloadToBlockRef(e.rollupCfg, payload.ExecutionPayload) if err != nil { - return eth.L2BlockRef{} + return nil, eth.L2BlockRef{} } - return ref + return payload, ref } // onInvalidPayload checks if the first next-up payload matches the invalid payload. @@ -1014,7 +1020,7 @@ func (e *EngineController) AddUnsafePayload(ctx context.Context, envelope *eth.E } p := e.unsafePayloads.Peek() e.metrics.RecordUnsafePayloadsBuffer(uint64(e.unsafePayloads.Len()), e.unsafePayloads.MemSize(), p.ExecutionPayload.ID()) - e.log.Trace("Next unsafe payload to process", "next", p.ExecutionPayload.ID(), "timestamp", uint64(p.ExecutionPayload.Timestamp)) + e.log.Debug("Next unsafe payload to process", "next", p.ExecutionPayload.ID(), "timestamp", uint64(p.ExecutionPayload.Timestamp)) // request forkchoice update directly so we can process the payload e.requestForkchoiceUpdate(ctx) diff --git a/op-node/rollup/engine/engine_controller_test.go b/op-node/rollup/engine/engine_controller_test.go index 481fafc2469..e3fb3a48a91 100644 --- a/op-node/rollup/engine/engine_controller_test.go +++ b/op-node/rollup/engine/engine_controller_test.go @@ -159,13 +159,16 @@ func TestLowestQueuedUnsafeBlock(t *testing.T) { ec := NewEngineController(context.Background(), nil, testlog.Logger(t, 0), metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, &testutils.MockL1Source{}, emitter) // empty -> zero - require.Equal(t, eth.L2BlockRef{}, ec.LowestQueuedUnsafeBlock()) + _, ref := ec.PeekUnsafePayload() + require.Equal(t, eth.L2BlockRef{}, ref) // queue -> returns derived ref _ = ec.unsafePayloads.Push(payloadA1) want, err := derive.PayloadToBlockRef(cfg, payloadA1.ExecutionPayload) require.NoError(t, err) - require.Equal(t, want, ec.LowestQueuedUnsafeBlock()) + + _, ref = ec.PeekUnsafePayload() + require.Equal(t, want, ref) } func TestLowestQueuedUnsafeBlock_OnDeriveErrorReturnsZero(t *testing.T) { @@ -175,7 +178,8 @@ func TestLowestQueuedUnsafeBlock_OnDeriveErrorReturnsZero(t *testing.T) { bad := ð.ExecutionPayloadEnvelope{ExecutionPayload: ð.ExecutionPayload{BlockNumber: 1, BlockHash: common.Hash{0xaa}}} _ = ec.unsafePayloads.Push(bad) - require.Equal(t, eth.L2BlockRef{}, ec.LowestQueuedUnsafeBlock()) + _, ref := ec.PeekUnsafePayload() + require.Equal(t, eth.L2BlockRef{}, ref) } func TestInvalidPayloadForNonHead_NoDrop(t *testing.T) { diff --git a/op-node/rollup/sync/config.go b/op-node/rollup/sync/config.go index 4e36092aaaf..ffe6af65c33 100644 --- a/op-node/rollup/sync/config.go +++ b/op-node/rollup/sync/config.go @@ -63,7 +63,9 @@ func (m *Mode) Clone() any { type Config struct { // SyncMode is defined above. - SyncMode Mode `json:"syncmode"` + SyncMode Mode `json:"syncmode"` + SyncModeReqResp bool `json:"syncmode_reqresp"` + // SkipSyncStartCheck skip the sanity check of consistency of L1 origins of the unsafe L2 blocks when determining the sync-starting point. // This defers the L1-origin verification, and is recommended to use in when utilizing --syncmode=execution-layer on op-node and --syncmode=snap on op-geth // Warning: This will be removed when we implement proper checkpoints. diff --git a/op-node/service.go b/op-node/service.go index 6c10e1718be..6e866c90b3d 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -340,6 +340,10 @@ func NewSyncConfig(ctx cliiface.Context, log log.Logger) (*sync.Config, error) { } else if ctx.IsSet(flags.L2EngineSyncEnabled.Name) { log.Error("l2.engine-sync is deprecated and will be removed in a future release. Use --syncmode=execution-layer instead.") } + // p2p.sync.req-resp=false && syncmode.req-resp=true is not allowed + if !ctx.Bool(flags.SyncReqRespName) && ctx.Bool(flags.SyncModeReqRespFlag.Name) { + return nil, errors.New("cannot set --p2p.sync.req-resp=false and --syncmode.req-resp=true at the same time") + } mode, err := sync.StringToMode(ctx.String(flags.SyncModeFlag.Name)) if err != nil { return nil, err @@ -348,6 +352,7 @@ func NewSyncConfig(ctx cliiface.Context, log log.Logger) (*sync.Config, error) { engineKind := engine.Kind(ctx.String(flags.L2EngineKind.Name)) cfg := &sync.Config{ SyncMode: mode, + SyncModeReqResp: ctx.Bool(flags.SyncModeReqRespFlag.Name), SkipSyncStartCheck: ctx.Bool(flags.SkipSyncStartCheck.Name), SupportsPostFinalizationELSync: engineKind.SupportsPostFinalizationELSync(), }