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

interop: Fix AtLeastAsSafe ; Geth Mempool Filter E2E Test #12823

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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 go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/ethereum/go-ethereum v1.14.11 => github.com/ethereum-optimism/op-geth v1.101411.1-rc.5
replace github.com/ethereum/go-ethereum v1.14.11 => github.com/ethereum-optimism/op-geth v1.101411.1-rc.6

//replace github.com/ethereum/go-ethereum => ../go-ethereum

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101411.1-rc.5 h1:LDSP85xTczjDYMBK0mOF5mzpZifLGz1TTW/6NgQsytc=
github.com/ethereum-optimism/op-geth v1.101411.1-rc.5/go.mod h1:7S4pp8KHBmEmKkRjL1BPOc6jY9hW+64YeMUjR3RVLw4=
github.com/ethereum-optimism/op-geth v1.101411.1-rc.6 h1:VvUBIVFbnU9486CWHa9Js5XYY3o6OsdQcI8gE3XjCDE=
github.com/ethereum-optimism/op-geth v1.101411.1-rc.6/go.mod h1:7S4pp8KHBmEmKkRjL1BPOc6jY9hW+64YeMUjR3RVLw4=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac h1:hCIrLuOPV3FJfMDvXeOhCC3uQNvFoMIIlkT2mN2cfeg=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac/go.mod h1:XaVXL9jg8BcyOeugECgIUGa9Y3DjYJj71RHmb5qon6M=
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
Expand Down
53 changes: 40 additions & 13 deletions op-e2e/interop/interop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/interopgen"
"github.com/ethereum-optimism/optimism/op-e2e/system/helpers"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
gethCore "github.com/ethereum/go-ethereum/core"
gethTypes "github.com/ethereum/go-ethereum/core/types"
)

// setupAndRun is a helper function that sets up a SuperSystem
// which contains two L2 Chains, and two users on each chain.
func setupAndRun(t *testing.T, fn func(*testing.T, SuperSystem)) {
func setupAndRun(t *testing.T, config SuperSystemConfig, fn func(*testing.T, SuperSystem)) {
recipe := interopgen.InteropDevRecipe{
L1ChainID: 900100,
L2ChainIDs: []uint64{900200, 900201},
Expand All @@ -38,7 +39,7 @@ func setupAndRun(t *testing.T, fn func(*testing.T, SuperSystem)) {

// create a super system from the recipe
// and get the L2 IDs for use in the test
s2 := NewSuperSystem(t, &recipe, worldResources)
s2 := NewSuperSystem(t, &recipe, worldResources, config)

// create two users on all L2 chains
s2.AddUser("Alice")
Expand Down Expand Up @@ -98,13 +99,16 @@ func TestInterop_IsolatedChains(t *testing.T) {
expectedBalance, _ = big.NewInt(0).SetString("10000000000000000000000000", 10)
require.Equal(t, expectedBalance, bobBalance)
}
setupAndRun(t, test)
config := SuperSystemConfig{
mempoolFiltering: false,
}
setupAndRun(t, config, test)
}

// TestInteropTrivial_EmitLogs tests a simple interop scenario
// TestInterop_EmitLogs tests a simple interop scenario
// Chains A and B exist, but no messages are sent between them.
// A contract is deployed on each chain, and logs are emitted repeatedly.
func TestInteropTrivial_EmitLogs(t *testing.T) {
func TestInterop_EmitLogs(t *testing.T) {
test := func(t *testing.T, s2 SuperSystem) {
ids := s2.L2IDs()
chainA := ids[0]
Expand Down Expand Up @@ -195,7 +199,10 @@ func TestInteropTrivial_EmitLogs(t *testing.T) {
requireMessage(chainB, log, types.CrossSafe, nil)
}
}
setupAndRun(t, test)
config := SuperSystemConfig{
mempoolFiltering: false,
}
setupAndRun(t, config, test)
}

func TestInteropBlockBuilding(t *testing.T) {
Expand Down Expand Up @@ -271,11 +278,17 @@ func TestInteropBlockBuilding(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
// Send an executing message, but with different payload.
// We expect the miner to be unable to include this tx, and confirmation to thus time out.
_, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, invalidPayload)
require.NotNil(t, err)
require.ErrorIs(t, err, ctx.Err())
require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded)
if s2.(*interopE2ESystem).config.mempoolFiltering {
// We expect the traqnsaction to be filtered out by the mempool if mempool filtering is enabled.
// ExecuteMessage the ErrTxFilteredOut error is checked when sending the tx.
_, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, invalidPayload, gethCore.ErrTxFilteredOut)
require.ErrorContains(t, err, gethCore.ErrTxFilteredOut.Error())
} else {
// We expect the miner to be unable to include this tx, and confirmation to thus time out, if mempool filtering is disabled.
_, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, invalidPayload, nil)
require.ErrorIs(t, err, ctx.Err())
require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded)
}
}

t.Log("Testing valid message now")
Expand All @@ -284,11 +297,25 @@ func TestInteropBlockBuilding(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
// Send an executing message with the correct identifier / payload
rec, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, msgPayload)
rec, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, msgPayload, nil)
require.NoError(t, err, "expecting tx to be confirmed")
t.Logf("confirmed executing msg in block %s", rec.BlockNumber)
}
t.Log("Done")
}
setupAndRun(t, test)

t.Run("without mempool filtering", func(t *testing.T) {
config := SuperSystemConfig{
mempoolFiltering: false,
}
setupAndRun(t, config, test)
})

t.Run("with mempool filtering", func(t *testing.T) {
config := SuperSystemConfig{
mempoolFiltering: true,
}
// run again with mempool filtering to observe the behavior of the mempool filter
setupAndRun(t, config, test)
})
}
22 changes: 19 additions & 3 deletions op-e2e/interop/supersystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,18 @@ type SuperSystem interface {
msgIdentifier supervisortypes.Identifier,
target common.Address,
message []byte,
expectedError error,
) (*types.Receipt, error)
// Access a contract on a network by name
Contract(network string, contractName string) interface{}
}
type SuperSystemConfig struct {
mempoolFiltering bool
}

// NewSuperSystem creates a new SuperSystem from a recipe. It creates an interopE2ESystem.
func NewSuperSystem(t *testing.T, recipe *interopgen.InteropDevRecipe, w worldResourcePaths) SuperSystem {
s2 := &interopE2ESystem{recipe: recipe}
func NewSuperSystem(t *testing.T, recipe *interopgen.InteropDevRecipe, w worldResourcePaths, config SuperSystemConfig) SuperSystem {
s2 := &interopE2ESystem{recipe: recipe, config: &config}
s2.prepare(t, w)
return s2
}
Expand All @@ -144,6 +148,7 @@ type interopE2ESystem struct {
l2GethClients map[string]*ethclient.Client
supervisor *supervisor.SupervisorService
superClient *sources.SupervisorClient
config *SuperSystemConfig
}

// l2Set is a set of resources for an L2 chain
Expand Down Expand Up @@ -263,6 +268,7 @@ func (s *interopE2ESystem) newGethForL2(id string, l2Out *interopgen.L2Output) *
l2Geth, err := geth.InitL2(name, l2Out.Genesis, jwtPath,
func(ethCfg *ethconfig.Config, nodeCfg *gn.Config) error {
ethCfg.InteropMessageRPC = s.supervisor.RPC()
ethCfg.InteropMempoolFiltering = s.config.mempoolFiltering
return nil
})
require.NoError(s.t, err)
Expand Down Expand Up @@ -721,13 +727,18 @@ func (s *interopE2ESystem) SendL2Tx(
newApply)
}

// ExecuteMessage calls the CrossL2Inbox executeMessage function
// it uses the L2's chain ID, username key, and geth client.
// expectedError represents the error returned by `ExecuteMessage` if it is expected.
// the returned err is related to `WaitMined`
func (s *interopE2ESystem) ExecuteMessage(
ctx context.Context,
id string,
sender string,
msgIdentifier supervisortypes.Identifier,
target common.Address,
message []byte,
expectedError error,
) (*types.Receipt, error) {
secret := s.UserKey(id, sender)
auth, err := bind.NewKeyedTransactorWithChainID(&secret, s.l2s[id].chainID)
Expand All @@ -746,7 +757,12 @@ func (s *interopE2ESystem) ExecuteMessage(
ChainId: msgIdentifier.ChainID.ToBig(),
}
tx, err := contract.InboxTransactor.ExecuteMessage(auth, identifier, target, message)
require.NoError(s.t, err)
if expectedError != nil {
require.ErrorContains(s.t, err, expectedError.Error())
return nil, err
} else {
require.NoError(s.t, err)
}
s.logger.Info("Executing message", "tx", tx.Hash(), "to", tx.To(), "target", target, "data", hexutil.Bytes(tx.Data()))
return bind.WaitMined(ctx, s.L2GethClient(id), tx)
}
Expand Down
28 changes: 18 additions & 10 deletions op-supervisor/supervisor/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,27 @@ func (lvl *SafetyLevel) UnmarshalText(text []byte) error {
}

// AtLeastAsSafe returns true if the receiver is at least as safe as the other SafetyLevel.
// Safety levels are assumed to graduate from LocalUnsafe to LocalSafe to CrossUnsafe to CrossSafe, with Finalized as the strongest.
func (lvl *SafetyLevel) AtLeastAsSafe(min SafetyLevel) bool {
switch min {
case Invalid:
return true
case CrossUnsafe:
return *lvl != Invalid
case CrossSafe:
return *lvl == CrossSafe || *lvl == Finalized
case Finalized:
return *lvl == Finalized
default:
relativeSafety := map[SafetyLevel]int{
Invalid: 0,
LocalUnsafe: 1,
LocalSafe: 2,
CrossUnsafe: 3,
CrossSafe: 4,
Finalized: 5,
}
// if either level is not recognized, return false
_, ok := relativeSafety[*lvl]
if !ok {
return false
}
_, ok = relativeSafety[min]
if !ok {
return false
}
// compare the relative safety levels to determine if the receiver is at least as safe as the other
return relativeSafety[*lvl] >= relativeSafety[min]
}

const (
Expand Down