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

✨ Adds interop delay option #255

Merged
merged 8 commits into from
Nov 13, 2024
Merged
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
1 change: 1 addition & 0 deletions config/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type NetworkConfig struct {
// check if Interop is enabled
InteropEnabled bool
InteropAutoRelay bool
InteropDelay uint64
}

type Chain interface {
Expand Down
9 changes: 9 additions & 0 deletions config/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (

InteropEnabledFlagName = "interop.enabled"
InteropAutoRelayFlagName = "interop.autorelay"
InteropDelayFlagName = "interop.delay"
)

var documentationLinks = []struct {
Expand Down Expand Up @@ -71,6 +72,12 @@ func BaseCLIFlags(envPrefix string) []cli.Flag {
Value: "",
EnvVars: opservice.PrefixEnvVar(envPrefix, "LOGS_DIRECTORY"),
},
&cli.Uint64Flag{
Name: InteropDelayFlagName,
Value: 0, // enabled by default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove comment

Usage: "Delay before relaying messages sent to the L2ToL2CrossDomainMessenger",
EnvVars: opservice.PrefixEnvVar(envPrefix, "INTEROP_DELAY"),
},
}
}

Expand Down Expand Up @@ -120,6 +127,7 @@ type CLIConfig struct {
L2StartingPort uint64

InteropAutoRelay bool
InteropDelay uint64

LogsDirectory string

Expand All @@ -134,6 +142,7 @@ func ReadCLIConfig(ctx *cli.Context) (*CLIConfig, error) {
L2StartingPort: ctx.Uint64(L2StartingPortFlagName),

InteropAutoRelay: ctx.Bool(InteropAutoRelayFlagName),
InteropDelay: ctx.Uint64(InteropDelayFlagName),

LogsDirectory: ctx.String(LogsDirectoryFlagName),
}
Expand Down
3 changes: 3 additions & 0 deletions docs/src/reference/supersim-fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ OPTIONS:
--interop.autorelay (default: false) ($SUPERSIM_INTEROP_AUTORELAY)
Automatically relay messages sent to the L2ToL2CrossDomainMessenger using
account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720

--interop.delay value (default: 0) ($SUPERSIM_INTEROP_DELAY)
Delay before relaying messages sent to the L2ToL2CrossDomainMessenger

--logs.directory value ($SUPERSIM_LOGS_DIRECTORY)
Directory to store logs
Expand Down
3 changes: 3 additions & 0 deletions docs/src/reference/supersim.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ GLOBAL OPTIONS:
Automatically relay messages sent to the L2ToL2CrossDomainMessenger using
account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720

--interop.delay value (default: 0) ($SUPERSIM_INTEROP_DELAY)
Delay before relaying messages sent to the L2ToL2CrossDomainMessenger

--l1.port value (default: 8545) ($SUPERSIM_L1_PORT)
Listening port for the L1 instance. `0` binds to any available port

Expand Down
19 changes: 18 additions & 1 deletion opsimulator/opsimulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type OpSimulator struct {

l1Chain config.Chain

interopDelay uint64

// Long running tasks
bgTasks tasks.Group
bgTasksCtx context.Context
Expand All @@ -60,7 +62,7 @@ type OpSimulator struct {
}

// OpSimulator wraps around the l2 chain. By embedding `Chain`, it also implements the same inteface
func New(log log.Logger, closeApp context.CancelCauseFunc, port uint64, l1Chain, l2Chain config.Chain, peers map[uint64]config.Chain) *OpSimulator {
func New(log log.Logger, closeApp context.CancelCauseFunc, port uint64, l1Chain, l2Chain config.Chain, peers map[uint64]config.Chain, interopDelay uint64) *OpSimulator {
bgTasksCtx, bgTasksCancel := context.WithCancel(context.Background())

crossL2Inbox, err := bindings.NewCrossL2Inbox(predeploys.CrossL2InboxAddr, l2Chain.EthClient())
Expand All @@ -75,6 +77,7 @@ func New(log log.Logger, closeApp context.CancelCauseFunc, port uint64, l1Chain,
port: port,
l1Chain: l1Chain,
crossL2Inbox: crossL2Inbox,
interopDelay: interopDelay,

bgTasksCtx: bgTasksCtx,
bgTasksCancel: bgTasksCancel,
Expand Down Expand Up @@ -411,6 +414,20 @@ func (opSim *OpSimulator) checkInteropInvariants(ctx context.Context, logs []typ
if common.BytesToHash(executingMessage.MsgHash[:]).Cmp(initiatingMsgPayloadHash) != 0 {
return fmt.Errorf("executing and initiating message fields are not equal")
}

if opSim.interopDelay != 0 {
// Add time check after getting the initiating message block header
header, err := opSim.ethClient.HeaderByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("failed to fetch executing block header: %w", err)
}

// Check if at least 5 seconds have passed
if header.Time < identifierBlockHeader.Time+opSim.interopDelay {
return fmt.Errorf("not enough time has passed since initiating message (need 5s, got %ds)",
header.Time-identifierBlockHeader.Time)
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewOrchestrator(log log.Logger, closeApp context.CancelCauseFunc, networkCo
// Sping up OpSim to fornt the L2 instances
for i := range networkConfig.L2Configs {
cfg := networkConfig.L2Configs[i]
l2OpSims[cfg.ChainID] = opsimulator.New(log, closeApp, nextL2Port, l1Anvil, l2Anvils[cfg.ChainID], l2Anvils)
l2OpSims[cfg.ChainID] = opsimulator.New(log, closeApp, nextL2Port, l1Anvil, l2Anvils[cfg.ChainID], l2Anvils, networkConfig.InteropDelay)

// only increment expected port if it has been specified
if nextL2Port > 0 {
Expand Down
1 change: 1 addition & 0 deletions supersim.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@

// Forward interop config
networkConfig.InteropAutoRelay = cliConfig.InteropAutoRelay
networkConfig.InteropDelay = cliConfig.InteropDelay

o, err := orchestrator.NewOrchestrator(log, closeApp, &networkConfig)
if err != nil {
Expand Down Expand Up @@ -109,7 +110,7 @@

fmt.Fprintln(&b, "Supersim Config")
fmt.Fprintln(&b, "-----------------------")
fmt.Fprintf(&b, s.adminServer.ConfigAsString())

Check failure on line 113 in supersim.go

View workflow job for this annotation

GitHub Actions / go-lint

SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)

fmt.Fprintln(&b, "Chain Configuration")
fmt.Fprintln(&b, "-----------------------")
Expand Down
112 changes: 112 additions & 0 deletions supersim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func createTestSuite(t *testing.T, cliConfig *config.CLIConfig) *TestSuite {

type ForkInteropTestSuiteOptions struct {
interopAutoRelay bool
interopDelay uint64
}

func createForkedInteropTestSuite(t *testing.T, testOptions ForkInteropTestSuiteOptions) *InteropTestSuite {
Expand All @@ -128,6 +129,7 @@ func createForkedInteropTestSuite(t *testing.T, testOptions ForkInteropTestSuite
InteropEnabled: true,
},
InteropAutoRelay: testOptions.interopAutoRelay,
InteropDelay: testOptions.interopDelay,
}
superchain := registry.Superchains[cliConfig.ForkConfig.Network]
srcChainCfg := config.OPChainByName(superchain, srcChain)
Expand Down Expand Up @@ -905,3 +907,113 @@ func TestForkAutoRelaySuperchainWETHTransferSucceeds(t *testing.T) {
})
assert.NoError(t, waitErr)
}

func TestInteropInvariantSucceedsWithDelay(t *testing.T) {
t.Parallel()

testSuite := createInteropTestSuite(t, config.CLIConfig{
InteropDelay: 2, // 2 second delay
})
privateKey, err := testSuite.DevKeys.Secret(devkeys.UserKey(0))
require.NoError(t, err)

l2ToL2CrossDomainMessenger, err := bindings.NewL2ToL2CrossDomainMessenger(predeploys.L2toL2CrossDomainMessengerAddr, testSuite.SourceEthClient)
require.NoError(t, err)

// Create initiating message using L2ToL2CrossDomainMessenger
origin := predeploys.L2toL2CrossDomainMessengerAddr
parsedSchemaRegistryAbi, _ := abi.JSON(strings.NewReader(opbindings.SchemaRegistryABI))
data, err := parsedSchemaRegistryAbi.Pack("register", "uint256 value", common.HexToAddress("0x0000000000000000000000000000000000000000"), false)
require.NoError(t, err)

sourceTransactor, err := bind.NewKeyedTransactorWithChainID(privateKey, testSuite.SourceChainID)
require.NoError(t, err)
tx, err := l2ToL2CrossDomainMessenger.SendMessage(sourceTransactor, testSuite.DestChainID, predeploys.SchemaRegistryAddr, data)
require.NoError(t, err)

initiatingMessageTxReceipt, err := bind.WaitMined(context.Background(), testSuite.SourceEthClient, tx)
require.NoError(t, err)
require.True(t, initiatingMessageTxReceipt.Status == 1, "initiating message transaction failed")

// Wait for delay time to pass
time.Sleep(2 * time.Second)

// progress forward blocks to ensure timestamps are updated
err = testSuite.DestEthClient.Client().CallContext(context.Background(), nil, "anvil_mine", uint64(3), uint64(2))
require.NoError(t, err)
err = testSuite.SourceEthClient.Client().CallContext(context.Background(), nil, "anvil_mine", uint64(3), uint64(2))
require.NoError(t, err)

l2tol2CDM, err := bindings.NewL2ToL2CrossDomainMessengerTransactor(predeploys.L2toL2CrossDomainMessengerAddr, testSuite.DestEthClient)
require.NoError(t, err)
initiatingMessageBlockHeader, err := testSuite.SourceEthClient.HeaderByNumber(context.Background(), initiatingMessageTxReceipt.BlockNumber)
require.NoError(t, err)
initiatingMessageLog := initiatingMessageTxReceipt.Logs[0]
identifier := bindings.ICrossL2InboxIdentifier{
Origin: origin,
BlockNumber: initiatingMessageTxReceipt.BlockNumber,
LogIndex: big.NewInt(0),
Timestamp: new(big.Int).SetUint64(initiatingMessageBlockHeader.Time),
ChainId: testSuite.SourceChainID,
}
transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, testSuite.DestChainID)
require.NoError(t, err)

// Should succeed because delay time has passed
tx, err = l2tol2CDM.RelayMessage(transactor, identifier, interop.ExecutingMessagePayloadBytes(initiatingMessageLog))
require.NoError(t, err)

receipt, err := bind.WaitMined(context.Background(), testSuite.DestEthClient, tx)
require.NoError(t, err)
require.True(t, receipt.Status == 1, "executing message transaction failed")
}

func TestInteropInvariantFailsWhenDelayTimeNotPassed(t *testing.T) {
t.Parallel()

testSuite := createInteropTestSuite(t, config.CLIConfig{
InteropDelay: 5,
})
privateKey, err := testSuite.DevKeys.Secret(devkeys.UserKey(0))
require.NoError(t, err)
fromAddress := crypto.PubkeyToAddress(privateKey.PublicKey)

l2ToL2CrossDomainMessenger, err := bindings.NewL2ToL2CrossDomainMessenger(predeploys.L2toL2CrossDomainMessengerAddr, testSuite.SourceEthClient)
require.NoError(t, err)

// Create initiating message using L2ToL2CrossDomainMessenger
origin := predeploys.L2toL2CrossDomainMessengerAddr
parsedSchemaRegistryAbi, _ := abi.JSON(strings.NewReader(opbindings.SchemaRegistryABI))
data, err := parsedSchemaRegistryAbi.Pack("register", "uint256 value", common.HexToAddress("0x0000000000000000000000000000000000000000"), false)
require.NoError(t, err)

sourceTransactor, err := bind.NewKeyedTransactorWithChainID(privateKey, testSuite.SourceChainID)
require.NoError(t, err)
tx, err := l2ToL2CrossDomainMessenger.SendMessage(sourceTransactor, testSuite.DestChainID, predeploys.SchemaRegistryAddr, data)
require.NoError(t, err)

initiatingMessageTxReceipt, err := bind.WaitMined(context.Background(), testSuite.SourceEthClient, tx)
require.NoError(t, err)
require.True(t, initiatingMessageTxReceipt.Status == 1, "initiating message transaction failed")

// Try to execute immediately without waiting for delay time
crossL2Inbox, err := bindings.NewCrossL2Inbox(predeploys.CrossL2InboxAddr, testSuite.DestEthClient)
require.NoError(t, err)
initiatingMessageBlockHeader, err := testSuite.SourceEthClient.HeaderByNumber(context.Background(), initiatingMessageTxReceipt.BlockNumber)
require.NoError(t, err)
initiatingMessageLog := initiatingMessageTxReceipt.Logs[0]
identifier := bindings.ICrossL2InboxIdentifier{
Origin: origin,
BlockNumber: initiatingMessageTxReceipt.BlockNumber,
LogIndex: big.NewInt(0),
Timestamp: new(big.Int).SetUint64(initiatingMessageBlockHeader.Time),
ChainId: testSuite.SourceChainID,
}
transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, testSuite.DestChainID)
require.NoError(t, err)

// Should fail because the delay time hasn't passed
_, err = crossL2Inbox.ExecuteMessage(transactor, identifier, fromAddress, initiatingMessageLog.Data)
require.Error(t, err)
require.Contains(t, err.Error(), "delay time not passed")
}
Loading