Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(op-e2e): Channel timeout late submission test #11995

Merged
merged 2 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
162 changes: 136 additions & 26 deletions op-e2e/actions/proofs/channel_timeout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,17 @@ import (

actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-program/client/claim"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

// Run a test that exercises the channel timeout functionality in `op-program`.
//
// Steps:
// 1. Build `NumL2Blocks` empty blocks on L2.
// 2. Buffer the first half of the L2 blocks in the batcher, and submit the frame data.
// 3. Time out the channel by mining `ChannelTimeout + 1` empty blocks on L1.
// 4. Submit the channel frame data across 2 transactions.
// 5. Instruct the sequencer to derive the L2 chain.
// 6. Run the FPP on the safe head.
// Run a test that submits the first channel frame, times out the channel, and then resubmits the full channel.
func runChannelTimeoutTest(gt *testing.T, testCfg *helpers.TestCfg[any]) {
t := actionsHelpers.NewDefaultTesting(gt)
tp := helpers.NewTestParams(func(tp *e2eutils.TestParams) {
// Set the channel timeout to 10 blocks, 12x lower than the sequencing window.
tp.ChannelTimeout = 10
})
tp := helpers.NewTestParams()
env := helpers.NewL2FaultProofEnv(t, testCfg, tp, helpers.NewBatcherCfg())
channelTimeout := env.Sd.ChainSpec.ChannelTimeout(0)

const NumL2Blocks = 10

Expand All @@ -40,9 +29,10 @@ func runChannelTimeoutTest(gt *testing.T, testCfg *helpers.TestCfg[any]) {
for i := 0; i < NumL2Blocks/2; i++ {
env.Batcher.ActL2BatchBuffer(t)
}
env.Batcher.ActL2BatchSubmit(t)
firstFrame := env.Batcher.ReadNextOutputFrame(t)
env.Batcher.ActL2BatchSubmitRaw(t, firstFrame)

// Instruct the batcher to submit the first channel frame to L1, and include the transaction.
// Include the batcher transaction.
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)
Expand All @@ -59,8 +49,8 @@ func runChannelTimeoutTest(gt *testing.T, testCfg *helpers.TestCfg[any]) {
l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(0), l2SafeHead.Number.Uint64())

// Time out the channel by mining `ChannelTimeout + 1` empty blocks on L1.
for i := uint64(0); i < tp.ChannelTimeout+1; i++ {
// Time out the channel by mining `channelTimeout + 1` empty blocks on L1.
for i := uint64(0); i < channelTimeout+1; i++ {
env.Miner.ActEmptyBlock(t)
env.Miner.ActL1SafeNext(t)
env.Miner.ActL1FinalizeNext(t)
Expand All @@ -77,17 +67,18 @@ func runChannelTimeoutTest(gt *testing.T, testCfg *helpers.TestCfg[any]) {
// Instruct the batcher to submit the blocks to L1 in a new channel,
// submitted across 2 transactions.
for i := 0; i < 2; i++ {
// Buffer half of the L2 chain's blocks.
for j := 0; j < NumL2Blocks/2; j++ {
env.Batcher.ActL2BatchBuffer(t)
}

// Close the channel on the second iteration.
if i == 1 {
if i == 0 {
// Re-submit the first frame
env.Batcher.ActL2BatchSubmitRaw(t, firstFrame)
} else {
// Buffer half of the L2 chain's blocks.
for j := 0; j < NumL2Blocks/2; j++ {
env.Batcher.ActL2BatchBuffer(t)
}
env.Batcher.ActL2ChannelClose(t)
env.Batcher.ActL2BatchSubmit(t)
}

env.Batcher.ActL2BatchSubmit(t)
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)
Expand All @@ -109,6 +100,110 @@ func runChannelTimeoutTest(gt *testing.T, testCfg *helpers.TestCfg[any]) {
env.RunFaultProofProgram(t, NumL2Blocks/2, testCfg.CheckResult, testCfg.InputParams...)
}

func runChannelTimeoutTest_CloseChannelLate(gt *testing.T, testCfg *helpers.TestCfg[any]) {
t := actionsHelpers.NewDefaultTesting(gt)
tp := helpers.NewTestParams()
env := helpers.NewL2FaultProofEnv(t, testCfg, tp, helpers.NewBatcherCfg())
channelTimeout := env.Sd.ChainSpec.ChannelTimeout(0)

const NumL2Blocks = 10

// Build NumL2Blocks empty blocks on L2
for i := 0; i < NumL2Blocks; i++ {
env.Sequencer.ActL2StartBlock(t)
env.Sequencer.ActL2EndBlock(t)
}

// Buffer the first half of L2 blocks in the batcher, and submit it.
for i := 0; i < NumL2Blocks/2; i++ {
env.Batcher.ActL2BatchBuffer(t)
}
firstFrame := env.Batcher.ReadNextOutputFrame(t)
env.Batcher.ActL2BatchSubmitRaw(t, firstFrame)

// Instruct the batcher to submit the first channel frame to L1, and include the transaction.
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)

// Finalize the block with the first channel frame on L1.
env.Miner.ActL1SafeNext(t)
env.Miner.ActL1FinalizeNext(t)
clabby marked this conversation as resolved.
Show resolved Hide resolved

// Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted.
env.Sequencer.ActL1HeadSignal(t)
env.Sequencer.ActL2PipelineFull(t)

// Ensure that the safe head has not advanced - the channel is incomplete.
l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(0), l2SafeHead.Number.Uint64())

// Time out the channel by mining `channelTimeout + 1` empty blocks on L1.
for i := uint64(0); i < channelTimeout+1; i++ {
env.Miner.ActEmptyBlock(t)
env.Miner.ActL1SafeNext(t)
env.Miner.ActL1FinalizeNext(t)
}

// Instruct the sequencer to derive the L2 chain.
env.Sequencer.ActL1HeadSignal(t)
env.Sequencer.ActL2PipelineFull(t)

// Ensure the safe head has still not advanced.
l2SafeHead = env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(0), l2SafeHead.Number.Uint64())

// Cache the second and final frame of the channel from the batcher, but do not submit it yet.
for i := 0; i < NumL2Blocks/2; i++ {
env.Batcher.ActL2BatchBuffer(t)
}
env.Batcher.ActL2ChannelClose(t)
finalFrame := env.Batcher.ReadNextOutputFrame(t)

// Submit the final frame of the timed out channel, now that the channel has timed out.
env.Batcher.ActL2BatchSubmitRaw(t, finalFrame)

// Instruct the batcher to submit the second channel frame to L1, and include the transaction.
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)

// Finalize the block with the second channel frame on L1.
env.Miner.ActL1SafeNext(t)
env.Miner.ActL1FinalizeNext(t)

// Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted.
env.Sequencer.ActL1HeadSignal(t)
env.Sequencer.ActL2PipelineFull(t)

// Ensure the safe head has still not advanced.
l2SafeHead = env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(0), l2SafeHead.Number.Uint64())

// Instruct the batcher to submit the blocks to L1 in a new channel.
for _, frame := range [][]byte{firstFrame, finalFrame} {
env.Batcher.ActL2BatchSubmitRaw(t, frame)
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)

// Finalize the block with the resubmitted channel frames on L1.
env.Miner.ActL1SafeNext(t)
env.Miner.ActL1FinalizeNext(t)
}

// Instruct the sequencer to derive the L2 chain.
env.Sequencer.ActL1HeadSignal(t)
env.Sequencer.ActL2PipelineFull(t)

// Ensure the safe head has still advanced to L2 block # NumL2Blocks.
l2SafeHead = env.Engine.L2Chain().CurrentSafeBlock()
require.EqualValues(t, NumL2Blocks, l2SafeHead.Number.Uint64())

// Run the FPP on L2 block # NumL2Blocks/2.
env.RunFaultProofProgram(t, NumL2Blocks/2, testCfg.CheckResult, testCfg.InputParams...)
}

func Test_ProgramAction_ChannelTimeout(gt *testing.T) {
matrix := helpers.NewMatrix[any]()
defer matrix.Run(gt)
Expand All @@ -128,4 +223,19 @@ func Test_ProgramAction_ChannelTimeout(gt *testing.T) {
helpers.ExpectError(claim.ErrClaimNotValid),
helpers.WithL2Claim(common.HexToHash("0xdeadbeef")),
)
matrix.AddTestCase(
"CloseChannelLate-HonestClaim",
nil,
helpers.LatestForkOnly,
runChannelTimeoutTest_CloseChannelLate,
helpers.ExpectNoError(),
)
matrix.AddTestCase(
"CloseChannelLate-JunkClaim",
nil,
helpers.LatestForkOnly,
runChannelTimeoutTest_CloseChannelLate,
helpers.ExpectError(claim.ErrClaimNotValid),
helpers.WithL2Claim(common.HexToHash("0xdeadbeef")),
)
}
6 changes: 3 additions & 3 deletions op-e2e/actions/proofs/helpers/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type L2FaultProofEnv struct {

func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eutils.TestParams, batcherCfg *helpers.BatcherCfg) *L2FaultProofEnv {
log := testlog.Logger(t, log.LvlDebug)
dp := NewDeployParams(t, func(dp *e2eutils.DeployParams) {
dp := NewDeployParams(t, tp, func(dp *e2eutils.DeployParams) {
genesisBlock := hexutil.Uint64(0)

// Enable cancun always
Expand Down Expand Up @@ -208,8 +208,8 @@ func NewTestParams(params ...TestParam) *e2eutils.TestParams {

type DeployParam func(p *e2eutils.DeployParams)

func NewDeployParams(t helpers.Testing, params ...DeployParam) *e2eutils.DeployParams {
dfault := e2eutils.MakeDeployParams(t, NewTestParams())
func NewDeployParams(t helpers.Testing, tp *e2eutils.TestParams, params ...DeployParam) *e2eutils.DeployParams {
dfault := e2eutils.MakeDeployParams(t, tp)
for _, apply := range params {
apply(dfault)
}
Expand Down
Loading