Skip to content

Commit

Permalink
op-e2e: Add action test for cascading reorgs in interop fault proofs (#…
Browse files Browse the repository at this point in the history
…14266)

* op-e2e: Add tests for cascading invalidations

* Make InboxContract an entity in the DSL.

* op-e2e: Focus test cases on consolidation.

* op-e2e: Generate correct payload when executing messages.

* op-e2e: Skip known failing test with reference to tracking issues.
  • Loading branch information
ajsutton authored and alcueca committed Feb 12, 2025
1 parent d6e7ce0 commit cdec591
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 42 deletions.
16 changes: 13 additions & 3 deletions op-e2e/actions/interop/dsl/dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type InteropDSL struct {
Outputs *Outputs
setup *InteropSetup

InboxContract *InboxContract

// allChains contains all chains in the interop set.
// Currently this is always two chains, but as the setup code becomes more flexible it could be more
// and likely this array would be replaced by something in InteropActors
Expand Down Expand Up @@ -91,6 +93,8 @@ func NewInteropDSL(t helpers.Testing) *InteropDSL {
},
setup: setup,

InboxContract: NewInboxContract(t),

allChains: allChains,
}
}
Expand All @@ -114,8 +118,8 @@ func (d *InteropDSL) CreateUser() *DSLUser {

type TransactionCreator func(chain *Chain) (*types.Transaction, common.Address)
type AddL2BlockOpts struct {
BlockIsNotCrossSafe bool
TransactionCreators []TransactionCreator
BlockIsNotCrossUnsafe bool
TransactionCreators []TransactionCreator
}

func WithL2BlockTransactions(mkTxs ...TransactionCreator) func(*AddL2BlockOpts) {
Expand All @@ -124,6 +128,12 @@ func WithL2BlockTransactions(mkTxs ...TransactionCreator) func(*AddL2BlockOpts)
}
}

func WithL1BlockCrossUnsafe() func(*AddL2BlockOpts) {
return func(o *AddL2BlockOpts) {
o.BlockIsNotCrossUnsafe = true
}
}

// AddL2Block adds a new unsafe block to the specified chain and fully processes it in the supervisor
func (d *InteropDSL) AddL2Block(chain *Chain, optionalArgs ...func(*AddL2BlockOpts)) {
opts := AddL2BlockOpts{}
Expand All @@ -145,7 +155,7 @@ func (d *InteropDSL) AddL2Block(chain *Chain, optionalArgs ...func(*AddL2BlockOp
status := chain.Sequencer.SyncStatus()
expectedBlockNum := priorSyncStatus.UnsafeL2.Number + 1
require.Equal(d.t, expectedBlockNum, status.UnsafeL2.Number, "Unsafe head did not advance")
if opts.BlockIsNotCrossSafe {
if opts.BlockIsNotCrossUnsafe {
require.Equal(d.t, priorSyncStatus.CrossUnsafeL2.Number, status.CrossUnsafeL2.Number, "CrossUnsafe head advanced unexpectedly")
} else {
require.Equal(d.t, expectedBlockNum, status.CrossUnsafeL2.Number, "CrossUnsafe head did not advance")
Expand Down
20 changes: 12 additions & 8 deletions op-e2e/actions/interop/dsl/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,44 @@ package dsl
import (
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/interop/contracts/bindings/emit"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)

type EmitterContract struct {
t helpers.Testing
bindings *emit.Emit
address common.Address
t helpers.Testing
addressByChain map[eth.ChainID]common.Address

EmittedMessages []*GeneratedTransaction
}

func NewEmitterContract(t helpers.Testing) *EmitterContract {
return &EmitterContract{
t: t,
t: t,
addressByChain: make(map[eth.ChainID]common.Address),
}
}

func (c *EmitterContract) Deploy(user *DSLUser) TransactionCreator {
return func(chain *Chain) (*types.Transaction, common.Address) {
opts, from := user.TransactOpts(chain)
emitContract, tx, emitBindings, err := emit.DeployEmit(opts, chain.SequencerEngine.EthClient())
emitContract, tx, _, err := emit.DeployEmit(opts, chain.SequencerEngine.EthClient())
require.NoError(c.t, err)
c.bindings = emitBindings
c.address = emitContract
c.addressByChain[chain.ChainID] = emitContract
return tx, from
}
}

func (c *EmitterContract) EmitMessage(user *DSLUser, message string) TransactionCreator {
return func(chain *Chain) (*types.Transaction, common.Address) {
opts, from := user.TransactOpts(chain)
tx, err := c.bindings.EmitData(opts, []byte(message))
address, ok := c.addressByChain[chain.ChainID]
require.Truef(c.t, ok, "not deployed on chain %d", chain.ChainID)
bindings, err := emit.NewEmitTransactor(address, chain.SequencerEngine.EthClient())
require.NoError(c.t, err)
tx, err := bindings.EmitData(opts, []byte(message))
require.NoError(c.t, err)
c.EmittedMessages = append(c.EmittedMessages, NewGeneratedTransaction(c.t, chain, tx))
return tx, from
Expand Down
8 changes: 8 additions & 0 deletions op-e2e/actions/interop/dsl/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/interop/contracts/bindings/inbox"
stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -40,6 +41,13 @@ func (m *GeneratedTransaction) Identifier() inbox.Identifier {
}
}

func (m *GeneratedTransaction) MessagePayload() []byte {
rcpt, err := m.chain.SequencerEngine.EthClient().TransactionReceipt(m.t.Ctx(), m.tx.Hash())
require.NoError(m.t, err)
require.NotZero(m.t, len(rcpt.Logs), "Transaction did not include any logs to reference")
return stypes.LogToMessagePayload(rcpt.Logs[0])
}

func (m *GeneratedTransaction) CheckIncluded() {
rcpt, err := m.chain.SequencerEngine.EthClient().TransactionReceipt(m.t.Ctx(), m.tx.Hash())
require.NoError(m.t, err)
Expand Down
177 changes: 146 additions & 31 deletions op-e2e/actions/interop/proofs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,58 @@ func TestInteropFaultProofs_TraceExtensionActivation(gt *testing.T) {
runFppAndChallengerTests(gt, system, tests)
}

func TestInteropFaultProofs_ConsolidateValidCrossChainMessage(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
system := dsl.NewInteropDSL(t)
actors := system.Actors

alice := system.CreateUser()
emitter := dsl.NewEmitterContract(t)
system.AddL2Block(actors.ChainA, dsl.WithL2BlockTransactions(emitter.Deploy(alice)))
system.AddL2Block(actors.ChainB, dsl.WithL2BlockTransactions(emitter.Deploy(alice)))

system.AddL2Block(system.Actors.ChainA, dsl.WithL2BlockTransactions(emitter.EmitMessage(alice, "hello")))
initMsg := emitter.LastEmittedMessage()
system.AddL2Block(system.Actors.ChainB, dsl.WithL2BlockTransactions(system.InboxContract.Execute(alice, initMsg.Identifier(), initMsg.MessagePayload())))

// Submit batch data for each chain in separate L1 blocks so tests can have one chain safe and one unsafe
system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) {
opts.SetChains(system.Actors.ChainA)
})
system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) {
opts.SetChains(system.Actors.ChainB)
})

endTimestamp := system.Actors.ChainA.Sequencer.L2Safe().Time
startTimestamp := endTimestamp - 1
end := system.Outputs.SuperRoot(endTimestamp)

paddingStep := func(step uint64) []byte {
return system.Outputs.TransitionState(startTimestamp, step,
system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp),
system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp),
).Marshal()
}

tests := []*transitionTest{
{
name: "Consolidate-AllValid",
agreedClaim: paddingStep(1023),
disputedClaim: end.Marshal(),
disputedTraceIndex: 1023,
expectValid: true,
},
{
name: "Consolidate-AllValid-InvalidNoChange",
agreedClaim: paddingStep(1023),
disputedClaim: paddingStep(1023),
disputedTraceIndex: 1023,
expectValid: false,
},
}
runFppAndChallengerTests(gt, system, tests)
}

func TestInteropFaultProofs(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
system := dsl.NewInteropDSL(t)
Expand Down Expand Up @@ -188,20 +240,6 @@ func TestInteropFaultProofs(gt *testing.T) {
disputedTraceIndex: 1022,
expectValid: true,
},
{
name: "Consolidate-AllValid",
agreedClaim: paddingStep(1023),
disputedClaim: end.Marshal(),
disputedTraceIndex: 1023,
expectValid: true,
},
{
name: "Consolidate-AllValid-InvalidNoChange",
agreedClaim: paddingStep(1023),
disputedClaim: paddingStep(1023),
disputedTraceIndex: 1023,
expectValid: false,
},
{
// The proposed block timestamp is after the unsafe head block timestamp.
// Expect to transition to invalid because the unsafe head is reached but challenger needs to handle
Expand Down Expand Up @@ -321,6 +359,97 @@ func TestInteropFaultProofs(gt *testing.T) {
runFppAndChallengerTests(gt, system, tests)
}

func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
// TODO(#14307): Support cascading invalidation in op-supervisor
t.Skip("Cascading invalidation not yet working")

system := dsl.NewInteropDSL(t)

actors := system.Actors
alice := system.CreateUser()
emitterContract := dsl.NewEmitterContract(t)
// Deploy emitter contract to both chains
system.AddL2Block(actors.ChainA, dsl.WithL2BlockTransactions(
emitterContract.Deploy(alice),
))
system.AddL2Block(actors.ChainB, dsl.WithL2BlockTransactions(
emitterContract.Deploy(alice),
))

// Initiating messages on chain A
system.AddL2Block(actors.ChainA, dsl.WithL2BlockTransactions(
emitterContract.EmitMessage(alice, "chainA message"),
))
chainAInitTx := emitterContract.LastEmittedMessage()
system.AddL2Block(actors.ChainB)
system.SubmitBatchData()

// Create a message with a conflicting payload on chain B, that also emits an initiating message
system.AddL2Block(actors.ChainB, dsl.WithL2BlockTransactions(
system.InboxContract.Execute(alice, chainAInitTx.Identifier(), []byte("this message was never emitted")),
emitterContract.EmitMessage(alice, "chainB message"),
), dsl.WithL1BlockCrossUnsafe())
chainBExecTx := system.InboxContract.LastTransaction()
chainBExecTx.CheckIncluded()
chainBInitTx := emitterContract.LastEmittedMessage()

// Create a message with a valid message on chain A, pointing to the initiating message on B from the same block
// as an invalid message.
system.AddL2Block(actors.ChainA,
dsl.WithL2BlockTransactions(system.InboxContract.Execute(alice, chainBInitTx.Identifier(), chainBInitTx.MessagePayload())),
// Block becomes cross-unsafe because the init msg is currently present, but it should not become cross-safe.
)
chainAExecTx := system.InboxContract.LastTransaction()
chainAExecTx.CheckIncluded()

system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) {
opts.SkipCrossSafeUpdate = true
})

endTimestamp := actors.ChainB.Sequencer.L2Unsafe().Time
startTimestamp := endTimestamp - 1
optimisticEnd := system.Outputs.SuperRoot(endTimestamp)

preConsolidation := system.Outputs.TransitionState(startTimestamp, 1023,
system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp),
system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp),
).Marshal()

// Induce block replacement
system.ProcessCrossSafe()
// assert that the invalid message txs were reorged out
chainBExecTx.CheckNotIncluded()
chainBInitTx.CheckNotIncluded() // Should have been reorged out with chainBExecTx
chainAExecTx.CheckNotIncluded() // Reorged out because chainBInitTx was reorged out

crossSafeEnd := system.Outputs.SuperRoot(endTimestamp)

tests := []*transitionTest{
{
name: "Consolidate-ExpectInvalidPendingBlock",
agreedClaim: preConsolidation,
disputedClaim: optimisticEnd.Marshal(),
disputedTraceIndex: 1023,
expectValid: false,
// TODO(#14306): Support cascading re-orgs in op-program
skipProgram: true,
skipChallenger: true,
},
{
name: "Consolidate-ReplaceInvalidBlocks",
agreedClaim: preConsolidation,
disputedClaim: crossSafeEnd.Marshal(),
disputedTraceIndex: 1023,
expectValid: true,
// TODO(#14306): Support cascading re-orgs in op-program
skipProgram: true,
skipChallenger: true,
},
}
runFppAndChallengerTests(gt, system, tests)
}

func TestInteropFaultProofsInvalidBlock(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)

Expand All @@ -344,23 +473,17 @@ func TestInteropFaultProofsInvalidBlock(gt *testing.T) {

// Create a message with a conflicting payload
fakeMessage := []byte("this message was never emitted")
inboxContract := dsl.NewInboxContract(t)
system.AddL2Block(actors.ChainB, func(opts *dsl.AddL2BlockOpts) {
opts.TransactionCreators = []dsl.TransactionCreator{inboxContract.Execute(alice, emitTx.Identifier(), fakeMessage)}
opts.BlockIsNotCrossSafe = true
opts.TransactionCreators = []dsl.TransactionCreator{system.InboxContract.Execute(alice, emitTx.Identifier(), fakeMessage)}
opts.BlockIsNotCrossUnsafe = true
})
system.AddL2Block(actors.ChainA)

// TODO: I wonder if it would be better to have `opts.ExpectInvalid` that specifies the invalid tx
// then the DSL can assert that it becomes local safe and is then reorged out automatically
// We could still grab the superroot and output roots for the invalid block while it is unsafe
// Other tests may still want to have SkipCrossUnsafeUpdate but generally nicer to be more declarative and
// high level to avoid leaking the details of when supervisor will trigger the reorg if possible.
system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) {
opts.SkipCrossSafeUpdate = true
})

execTx := inboxContract.LastTransaction()
execTx := system.InboxContract.LastTransaction()
execTx.CheckIncluded()

// safe head is still behind until we verify cross-safe
Expand Down Expand Up @@ -461,14 +584,6 @@ func TestInteropFaultProofsInvalidBlock(gt *testing.T) {
skipProgram: true,
skipChallenger: true,
},
{
name: "Consolidate-ReplaceBlockInvalidatedByFirstInvalidatedBlock",
// Will need to generate an invalid block before this can be enabled
// Check that if a block B depends on a log in block A, and block A is found to have an invalid message
// that block B is also replaced with a deposit only block because A no longer contains the log it needs
skipProgram: true,
skipChallenger: true,
},
{
name: "AlreadyAtClaimedTimestamp",
agreedClaim: crossSafeSuperRootEnd.Marshal(),
Expand Down

0 comments on commit cdec591

Please sign in to comment.