Skip to content

Commit

Permalink
feat(op-e2e): Recover from bad transactions in batch test (#12039)
Browse files Browse the repository at this point in the history
* feat(op-e2e): Recover from bad transactions in batch test

* fix

* clean

* naming nit
  • Loading branch information
clabby authored Sep 22, 2024
1 parent dd7f5db commit 53080c9
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
21 changes: 21 additions & 0 deletions op-e2e/actions/helpers/l2_batcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,27 @@ func (s *L2Batcher) SubmittingData() bool {
return s.l2Submitting
}

// Reset the batcher state, clearing any buffered data.
func (s *L2Batcher) Reset() {
s.L2ChannelOut = nil
s.l2Submitting = false
s.L2BufferedBlock = eth.L2BlockRef{}
s.l2SubmittedBlock = eth.L2BlockRef{}
}

// ActL2BatchBuffer adds the next L2 block to the batch buffer.
// If the buffer is being submitted, the buffer is wiped.
func (s *L2Batcher) ActL2BatchBuffer(t Testing) {
require.NoError(t, s.Buffer(t), "failed to add block to channel")
}

func (s *L2Batcher) Buffer(t Testing) error {
return s.BufferWithOpts(t)
}

type BlockModifier = func(block *types.Block)

func (s *L2Batcher) BufferWithOpts(t Testing, opts ...BlockModifier) error {
if s.l2Submitting { // break ongoing submitting work if necessary
s.L2ChannelOut = nil
s.l2Submitting = false
Expand Down Expand Up @@ -175,6 +189,7 @@ func (s *L2Batcher) Buffer(t Testing) error {
return nil
}
}

block, err := s.l2.BlockByNumber(t.Ctx(), big.NewInt(int64(s.L2BufferedBlock.Number+1)))
require.NoError(t, err, "need l2 block %d from sync status", s.l2SubmittedBlock.Number+1)
if block.ParentHash() != s.L2BufferedBlock.Hash {
Expand All @@ -183,6 +198,12 @@ func (s *L2Batcher) Buffer(t Testing) error {
s.L2BufferedBlock = syncStatus.SafeL2
s.L2ChannelOut = nil
}

// Apply modifications to the block
for _, f := range opts {
f(block)
}

// Create channel if we don't have one yet
if s.L2ChannelOut == nil {
var ch ChannelOutIface
Expand Down
4 changes: 4 additions & 0 deletions op-e2e/actions/helpers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func (s *BasicUser[B]) SetUserEnv(env *BasicUserEnv[B]) {
s.env = env
}

func (s *BasicUser[B]) Signer() types.Signer {
return s.env.Signer
}

func (s *BasicUser[B]) signerFn(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != s.address {
return nil, bind.ErrNotAuthorized
Expand Down
174 changes: 174 additions & 0 deletions op-e2e/actions/proofs/bad_tx_in_batch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package proofs

import (
"testing"

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-program/client/claim"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)

func runBadTxInBatchTest(gt *testing.T, testCfg *helpers.TestCfg[any]) {
t := actionsHelpers.NewDefaultTesting(gt)
env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg())

// Build a block on L2 with 1 tx.
env.Alice.L2.ActResetTxOpts(t)
env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob)
env.Alice.L2.ActMakeTx(t)

env.Sequencer.ActL2StartBlock(t)
env.Engine.ActL2IncludeTx(env.Alice.Address())(t)
env.Sequencer.ActL2EndBlock(t)
env.Alice.L2.ActCheckReceiptStatusOfLastTx(true)(t)

// Instruct the batcher to submit a faulty channel, with an invalid tx.
err := env.Batcher.BufferWithOpts(t, func(block *types.Block) {
// Replace the tx with one that has a bad signature.
txs := block.Transactions()
newTx, err := txs[1].WithSignature(env.Alice.L2.Signer(), make([]byte, 65))
txs[1] = newTx
require.NoError(t, err)
})
require.NoError(t, err)
env.Batcher.ActL2ChannelClose(t)
env.Batcher.ActL2BatchSubmit(t)

// Include the batcher transaction.
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)
env.Miner.ActL1SafeNext(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 not advanced - the batch is invalid.
l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(0), l2SafeHead.Number.Uint64())

// Reset the batcher and submit a valid batch.
env.Batcher.Reset()
env.Batcher.ActSubmitAll(t)
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)
env.Miner.ActL1SafeNext(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 advanced.
l1Head := env.Miner.L1Chain().CurrentBlock()
l2SafeHead = env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(2), l1Head.Number.Uint64())
require.Equal(t, uint64(1), l2SafeHead.Number.Uint64())

env.RunFaultProofProgram(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, testCfg.InputParams...)
}

func runBadTxInBatch_ResubmitBadFirstFrame_Test(gt *testing.T, testCfg *helpers.TestCfg[any]) {
t := actionsHelpers.NewDefaultTesting(gt)
env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg())

// Build 2 blocks on L2 with 1 tx each.
for i := 0; i < 2; i++ {
env.Alice.L2.ActResetTxOpts(t)
env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob)
env.Alice.L2.ActMakeTx(t)

env.Sequencer.ActL2StartBlock(t)
env.Engine.ActL2IncludeTx(env.Alice.Address())(t)
env.Sequencer.ActL2EndBlock(t)
env.Alice.L2.ActCheckReceiptStatusOfLastTx(true)(t)
}

// Instruct the batcher to submit a faulty channel, with an invalid tx in the second block
// within the span batch.
env.Batcher.ActL2BatchBuffer(t)
err := env.Batcher.BufferWithOpts(t, func(block *types.Block) {
// Replace the tx with one that has a bad signature.
txs := block.Transactions()
newTx, err := txs[1].WithSignature(env.Alice.L2.Signer(), make([]byte, 65))
txs[1] = newTx
require.NoError(t, err)
})
require.NoError(t, err)
env.Batcher.ActL2ChannelClose(t)
env.Batcher.ActL2BatchSubmit(t)

// Include the batcher transaction.
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)
env.Miner.ActL1SafeNext(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 not advanced - the batch is invalid.
l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(0), l2SafeHead.Number.Uint64())

// Reset the batcher and submit a valid batch.
env.Batcher.Reset()
env.Batcher.ActSubmitAll(t)
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t)
env.Miner.ActL1EndBlock(t)
env.Miner.ActL1SafeNext(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 advanced.
l1Head := env.Miner.L1Chain().CurrentBlock()
l2SafeHead = env.Engine.L2Chain().CurrentSafeBlock()
require.Equal(t, uint64(2), l1Head.Number.Uint64())
require.Equal(t, uint64(2), l2SafeHead.Number.Uint64())

env.RunFaultProofProgram(t, l2SafeHead.Number.Uint64()-1, testCfg.CheckResult, testCfg.InputParams...)
}

func Test_ProgramAction_BadTxInBatch(gt *testing.T) {
matrix := helpers.NewMatrix[any]()
defer matrix.Run(gt)

matrix.AddTestCase(
"HonestClaim",
nil,
helpers.LatestForkOnly,
runBadTxInBatchTest,
helpers.ExpectNoError(),
)
matrix.AddTestCase(
"JunkClaim",
nil,
helpers.LatestForkOnly,
runBadTxInBatchTest,
helpers.ExpectError(claim.ErrClaimNotValid),
helpers.WithL2Claim(common.HexToHash("0xdeadbeef")),
)
matrix.AddTestCase(
"ResubmitBadFirstFrame-HonestClaim",
nil,
helpers.LatestForkOnly,
runBadTxInBatch_ResubmitBadFirstFrame_Test,
helpers.ExpectNoError(),
)
matrix.AddTestCase(
"ResubmitBadFirstFrame-JunkClaim",
nil,
helpers.LatestForkOnly,
runBadTxInBatch_ResubmitBadFirstFrame_Test,
helpers.ExpectError(claim.ErrClaimNotValid),
helpers.WithL2Claim(common.HexToHash("0xdeadbeef")),
)
}

0 comments on commit 53080c9

Please sign in to comment.