From a6eb5983b17497630fc0fa72f7b7d9a53ceb7ce2 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 26 Aug 2024 18:34:30 -0600 Subject: [PATCH 1/3] interop: experimental sequencer executing message check support Includes miner change, to mark invalid interop transactions as rejected --- cmd/geth/main.go | 2 + cmd/utils/flags.go | 17 ++++ core/types/interoptypes/interop.go | 158 +++++++++++++++++++++++++++++ eth/backend.go | 22 ++++ eth/ethconfig/config.go | 3 + eth/ethconfig/gen_config.go | 12 +++ eth/interop/interop.go | 24 +++++ fork.yaml | 1 + miner/miner.go | 5 + miner/payload_building.go | 12 +++ miner/worker.go | 48 ++++++++- params/interop.go | 5 + 12 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 core/types/interoptypes/interop.go create mode 100644 eth/interop/interop.go create mode 100644 params/interop.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3ac62eb355..226e3fe86f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -159,6 +159,8 @@ var ( utils.RollupSequencerTxConditionalRateLimitFlag, utils.RollupHistoricalRPCFlag, utils.RollupHistoricalRPCTimeoutFlag, + utils.RollupInteropRPCFlag, + utils.RollupInteropRPCTimeoutFlag, utils.RollupDisableTxPoolGossipFlag, utils.RollupComputePendingBlock, utils.RollupHaltOnIncompatibleProtocolVersionFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 74de8611e1..88d17831a8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -949,6 +949,19 @@ var ( Category: flags.RollupCategory, } + RollupInteropRPCFlag = &cli.StringFlag{ + Name: "rollup.interoprpc", + Usage: "RPC endpoint for interop message verification (experimental).", + Category: flags.RollupCategory, + } + + RollupInteropRPCTimeoutFlag = &cli.StringFlag{ + Name: "rollup.interoprpctimeout", + Usage: "Timeout for interop RPC dial (experimental).", + Value: "5s", + Category: flags.RollupCategory, + } + RollupDisableTxPoolGossipFlag = &cli.BoolFlag{ Name: "rollup.disabletxpoolgossip", Usage: "Disable transaction pool gossip.", @@ -1966,6 +1979,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(RollupHistoricalRPCTimeoutFlag.Name) { cfg.RollupHistoricalRPCTimeout = ctx.Duration(RollupHistoricalRPCTimeoutFlag.Name) } + if ctx.IsSet(RollupInteropRPCFlag.Name) { + cfg.InteropMessageRPC = ctx.String(RollupInteropRPCFlag.Name) + } + cfg.InteropMessageRPCTimeout = ctx.Duration(RollupInteropRPCTimeoutFlag.Name) cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupDisableTxPoolGossipFlag.Name) cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name) cfg.RollupHaltOnIncompatibleProtocolVersion = ctx.String(RollupHaltOnIncompatibleProtocolVersionFlag.Name) diff --git a/core/types/interoptypes/interop.go b/core/types/interoptypes/interop.go new file mode 100644 index 0000000000..2e52928b6b --- /dev/null +++ b/core/types/interoptypes/interop.go @@ -0,0 +1,158 @@ +package interoptypes + +import ( + "encoding/binary" + "encoding/json" + "errors" + "fmt" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +var ExecutingMessageEventTopic = crypto.Keccak256Hash([]byte("ExecutingMessage(bytes32,(address,uint256,uint256,uint256,uint256))")) + +type Message struct { + Identifier Identifier `json:"identifier"` + PayloadHash common.Hash `json:"payloadHash"` +} + +func (m *Message) DecodeEvent(topics []common.Hash, data []byte) error { + if len(topics) != 2 { // event hash, indexed payloadHash + return fmt.Errorf("unexpected number of event topics: %d", len(topics)) + } + if topics[0] != ExecutingMessageEventTopic { + return fmt.Errorf("unexpected event topic %q", topics[0]) + } + if len(data) != 32*5 { + return fmt.Errorf("unexpected identifier data length: %d", len(data)) + } + take := func(length uint) []byte { + taken := data[:length] + data = data[length:] + return taken + } + takeZeroes := func(length uint) error { + for _, v := range take(length) { + if v != 0 { + return errors.New("expected zero") + } + } + return nil + } + if err := takeZeroes(12); err != nil { + return fmt.Errorf("invalid address padding: %w", err) + } + m.Identifier.Origin = common.Address(take(20)) + if err := takeZeroes(32 - 8); err != nil { + return fmt.Errorf("invalid block number padding: %w", err) + } + m.Identifier.BlockNumber = binary.BigEndian.Uint64(take(8)) + if err := takeZeroes(32 - 8); err != nil { + return fmt.Errorf("invalid log index padding: %w", err) + } + m.Identifier.LogIndex = binary.BigEndian.Uint64(take(8)) + if err := takeZeroes(32 - 8); err != nil { + return fmt.Errorf("invalid timestamp padding: %w", err) + } + m.Identifier.Timestamp = binary.BigEndian.Uint64(take(8)) + m.Identifier.ChainID.SetBytes32(take(32)) + m.PayloadHash = topics[1] + return nil +} + +func ExecutingMessagesFromLogs(logs []*types.Log) ([]Message, error) { + var executingMessages []Message + for i, l := range logs { + if l.Address == params.InteropCrossL2InboxAddress { + var msg Message + if err := msg.DecodeEvent(l.Topics, l.Data); err != nil { + return nil, fmt.Errorf("invalid executing message %d, tx-log %d: %w", len(executingMessages), i, err) + } + executingMessages = append(executingMessages, msg) + } + } + return executingMessages, nil +} + +type Identifier struct { + Origin common.Address + BlockNumber uint64 + LogIndex uint64 + Timestamp uint64 + ChainID uint256.Int // flat, not a pointer, to make Identifier safe as map key +} + +type identifierMarshaling struct { + Origin common.Address `json:"origin"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + LogIndex hexutil.Uint64 `json:"logIndex"` + Timestamp hexutil.Uint64 `json:"timestamp"` + ChainID hexutil.U256 `json:"chainID"` +} + +func (id Identifier) MarshalJSON() ([]byte, error) { + var enc identifierMarshaling + enc.Origin = id.Origin + enc.BlockNumber = hexutil.Uint64(id.BlockNumber) + enc.LogIndex = hexutil.Uint64(id.LogIndex) + enc.Timestamp = hexutil.Uint64(id.Timestamp) + enc.ChainID = (hexutil.U256)(id.ChainID) + return json.Marshal(&enc) +} + +func (id *Identifier) UnmarshalJSON(input []byte) error { + var dec identifierMarshaling + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + id.Origin = dec.Origin + id.BlockNumber = uint64(dec.BlockNumber) + id.LogIndex = uint64(dec.LogIndex) + id.Timestamp = uint64(dec.Timestamp) + id.ChainID = (uint256.Int)(dec.ChainID) + return nil +} + +type SafetyLevel string + +func (lvl SafetyLevel) String() string { + return string(lvl) +} + +func (lvl SafetyLevel) Valid() bool { + switch lvl { + case Finalized, Safe, CrossUnsafe, Unsafe: + return true + default: + return false + } +} + +func (lvl SafetyLevel) MarshalText() ([]byte, error) { + return []byte(lvl), nil +} + +func (lvl *SafetyLevel) UnmarshalText(text []byte) error { + if lvl == nil { + return errors.New("cannot unmarshal into nil SafetyLevel") + } + x := SafetyLevel(text) + if !x.Valid() { + return fmt.Errorf("unrecognized safety level: %q", text) + } + *lvl = x + return nil +} + +const ( + Finalized SafetyLevel = "finalized" + Safe SafetyLevel = "safe" + CrossUnsafe SafetyLevel = "cross-unsafe" + Unsafe SafetyLevel = "unsafe" +) diff --git a/eth/backend.go b/eth/backend.go index d0dd93ec4a..73324d07bd 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,6 +20,7 @@ package eth import ( "context" "encoding/json" + "errors" "fmt" "math/big" "runtime" @@ -38,10 +39,12 @@ import ( "github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/interoptypes" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/interop" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/eth/tracers" @@ -80,6 +83,8 @@ type Ethereum struct { seqRPCService *rpc.Client historicalRPCService *rpc.Client + interopRPC *interop.InteropClient + // DB interfaces chainDb ethdb.Database // Block chain database @@ -336,6 +341,16 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.historicalRPCService = client } + if config.InteropMessageRPC != "" { + ctx, cancel := context.WithTimeout(context.Background(), config.InteropMessageRPCTimeout) + client, err := interop.DialClient(ctx, config.InteropMessageRPC) + cancel() + if err != nil { + return nil, fmt.Errorf("failed to dial Interop RPC %q: %w", config.InteropMessageRPC, err) + } + eth.interopRPC = client + } + // Start the RPC service eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID) @@ -540,3 +555,10 @@ func (s *Ethereum) HandleRequiredProtocolVersion(required params.ProtocolVersion } return nil } + +func (s *Ethereum) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error { + if s.interopRPC == nil { + return errors.New("cannot check interop messages, no RPC available") + } + return s.interopRPC.CheckMessages(ctx, messages, minSafety) +} diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 28849a8519..ce56b10100 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -190,6 +190,9 @@ type Config struct { RollupDisableTxPoolGossip bool RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string + + InteropMessageRPC string + InteropMessageRPCTimeout time.Duration } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index a8ffe914a4..e573cc583f 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -72,6 +72,8 @@ func (c Config) MarshalTOML() (interface{}, error) { RollupDisableTxPoolGossip bool RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string + InteropMessageRPC string + InteropMessageRPCTimeout time.Duration } var enc Config enc.Genesis = c.Genesis @@ -129,6 +131,8 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RollupDisableTxPoolGossip = c.RollupDisableTxPoolGossip enc.RollupDisableTxPoolAdmission = c.RollupDisableTxPoolAdmission enc.RollupHaltOnIncompatibleProtocolVersion = c.RollupHaltOnIncompatibleProtocolVersion + enc.InteropMessageRPC = c.InteropMessageRPC + enc.InteropMessageRPCTimeout = c.InteropMessageRPCTimeout return &enc, nil } @@ -190,6 +194,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RollupDisableTxPoolGossip *bool RollupDisableTxPoolAdmission *bool RollupHaltOnIncompatibleProtocolVersion *string + InteropMessageRPC *string + InteropMessageRPCTimeout *time.Duration } var dec Config if err := unmarshal(&dec); err != nil { @@ -360,5 +366,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.RollupHaltOnIncompatibleProtocolVersion != nil { c.RollupHaltOnIncompatibleProtocolVersion = *dec.RollupHaltOnIncompatibleProtocolVersion } + if dec.InteropMessageRPC != nil { + c.InteropMessageRPC = *dec.InteropMessageRPC + } + if dec.InteropMessageRPCTimeout != nil { + c.InteropMessageRPCTimeout = *dec.InteropMessageRPCTimeout + } return nil } diff --git a/eth/interop/interop.go b/eth/interop/interop.go new file mode 100644 index 0000000000..f0aaf7b8c9 --- /dev/null +++ b/eth/interop/interop.go @@ -0,0 +1,24 @@ +package interop + +import ( + "context" + + "github.com/ethereum/go-ethereum/core/types/interoptypes" + "github.com/ethereum/go-ethereum/rpc" +) + +type InteropClient struct { + rpcClient *rpc.Client +} + +func DialClient(ctx context.Context, rpcEndpoint string) (*InteropClient, error) { + cl, err := rpc.DialContext(ctx, rpcEndpoint) + if err != nil { + return nil, err + } + return &InteropClient{rpcClient: cl}, nil +} + +func (cl *InteropClient) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error { + return cl.rpcClient.CallContext(ctx, nil, "supervisor_checkMessages", messages, minSafety) +} diff --git a/fork.yaml b/fork.yaml index 1f8f790bde..41537cfd88 100644 --- a/fork.yaml +++ b/fork.yaml @@ -114,6 +114,7 @@ def: description: | The block-building code (in the "miner" package because of Proof-Of-Work legacy of ethereum) implements the changes to support the transaction-inclusion, tx-pool toggle and gaslimit parameters of the Engine API. + This also includes experimental support for interop executing-messages to be verified through an RPC. globs: - "miner/*" - title: "Tx-pool tx cost updates" diff --git a/miner/miner.go b/miner/miner.go index 2e14bb10fe..717a01a252 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/interoptypes" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/params" "golang.org/x/time/rate" @@ -47,6 +48,10 @@ type BackendWithHistoricalState interface { StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) } +type BackendWithInterop interface { + CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error +} + // Config is the configuration parameters of mining. type Config struct { Etherbase common.Address `toml:"-"` // Deprecated diff --git a/miner/payload_building.go b/miner/payload_building.go index 1577acc6ae..9bc00ef412 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -17,6 +17,7 @@ package miner import ( + "context" "crypto/sha256" "encoding/binary" "errors" @@ -98,16 +99,23 @@ type Payload struct { err error stopOnce sync.Once interrupt *atomic.Int32 // interrupt signal shared with worker + + rpcCtx context.Context + rpcCancel context.CancelFunc } // newPayload initializes the payload object. func newPayload(empty *types.Block, id engine.PayloadID) *Payload { + rpcCtx, rpcCancel := context.WithCancel(context.Background()) payload := &Payload{ id: id, empty: empty, stop: make(chan struct{}), interrupt: new(atomic.Int32), + + rpcCtx: rpcCtx, + rpcCancel: rpcCancel, } log.Info("Starting work on payload", "id", payload.id) payload.cond = sync.NewCond(&payload.lock) @@ -229,6 +237,7 @@ func (payload *Payload) resolve(onlyFull bool) *engine.ExecutionPayloadEnvelope // the update anyways. // interruptBuilding is safe to be called concurrently. func (payload *Payload) interruptBuilding() { + payload.rpcCancel() // Set the interrupt if not interrupted already. // It's ok if it has either already been interrupted by payload resolution earlier, // or by the timeout timer set to commitInterruptTimeout. @@ -245,6 +254,7 @@ func (payload *Payload) interruptBuilding() { // transactions with interruptBuilding. // stopBuilding is safe to be called concurrently. func (payload *Payload) stopBuilding() { + payload.rpcCancel() // Concurrent Resolve calls should only stop once. payload.stopOnce.Do(func() { log.Debug("Stop payload building.", "id", payload.id) @@ -270,6 +280,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) { noTxs: true, txs: args.Transactions, gasLimit: args.GasLimit, + rpcCtx: nil, // No RPC requests allowed. } empty := miner.generateWork(emptyParams) if empty.err != nil { @@ -306,6 +317,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) { payload := newPayload(nil, args.Id()) // set shared interrupt fullParams.interrupt = payload.interrupt + fullParams.rpcCtx = payload.rpcCtx // Spin up a routine for updating the payload in background. This strategy // can maximum the revenue for including transactions with highest fee. diff --git a/miner/worker.go b/miner/worker.go index d17199b89f..843725b523 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/interoptypes" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" @@ -73,6 +74,9 @@ type environment struct { receipts []*types.Receipt sidecars []*types.BlobTxSidecar blobs int + + noTxs bool // true if we are reproducing a block, and do not have to check interop txs + rpcCtx context.Context // context to control block-building RPC work. No RPC allowed if nil. } const ( @@ -108,6 +112,8 @@ type generateParams struct { gasLimit *uint64 // Optional gas limit override interrupt *atomic.Int32 // Optional interruption signal to pass down to worker.generateWork isUpdate bool // Optional flag indicating that this is building a discardable update + + rpcCtx context.Context // context to control block-building RPC work. No RPC allowed if nil. } // generateWork generates a sealing block based on the given parameters. @@ -248,11 +254,12 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) // Could potentially happen if starting to mine in an odd state. // Note genParams.coinbase can be different with header.Coinbase // since clique algorithm can modify the coinbase field in header. - env, err := miner.makeEnv(parent, header, genParams.coinbase) + env, err := miner.makeEnv(parent, header, genParams.coinbase, genParams.rpcCtx) if err != nil { log.Error("Failed to create sealing context", "err", err) return nil, err } + env.noTxs = genParams.noTxs if header.ParentBeaconRoot != nil { context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) @@ -262,7 +269,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) } // makeEnv creates a new environment for the sealing block. -func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address) (*environment, error) { +func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address, rpcCtx context.Context) (*environment, error) { // Retrieve the parent state to execute on top. state, err := miner.chain.StateAt(parent.Root) if err != nil { @@ -287,6 +294,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase state: state, coinbase: coinbase, header: header, + rpcCtx: rpcCtx, }, nil } @@ -360,6 +368,13 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* gp = env.gasPool.Gas() ) receipt, err := core.ApplyTransaction(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{}) + // If successful, and not just reproducing the block, check the interop executing messages. + if err == nil && !env.noTxs && miner.chain.Config().IsInterop(env.header.Time) { + // Whenever there are `noTxs` it means we are building a block from pre-determined txs. There are two cases: + // (1) it's derived from L1, and will be verified asynchronously by the op-node. + // (2) it is a deposits-only empty-block by the sequencer, in which case there are no interop-txs to verify (as deposits do not emit any). + err = miner.checkInterop(env.rpcCtx, tx, receipt) + } if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) @@ -367,6 +382,31 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* return receipt, err } +func (miner *Miner) checkInterop(ctx context.Context, tx *types.Transaction, receipt *types.Receipt) error { + b, ok := miner.backend.(BackendWithInterop) + if !ok { + return fmt.Errorf("cannot mine interop txs without interop backend, got backend type %T", miner.backend) + } + if ctx == nil { // check if the miner was set up correctly to interact with an RPC + return errors.New("need RPC context to check executing messages") + } + executingMessages, err := interoptypes.ExecutingMessagesFromLogs(receipt.Logs) + if err != nil { + return fmt.Errorf("cannot parse interop messages from receipt of %s: %w", receipt.TxHash, err) + } + if len(executingMessages) == 0 { + return nil // avoid an RPC check if there are no executing messages to verify. + } + if err := b.CheckMessages(ctx, executingMessages, interoptypes.CrossUnsafe); err != nil { + if ctx.Err() != nil { // don't reject transactions permanently on RPC timeouts etc. + return err + } + tx.SetRejected() // Mark the tx as rejected: it will not be welcome in the tx-pool anymore. + return err + } + return nil +} + func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { gasLimit := env.header.GasLimit if env.gasPool == nil { @@ -470,6 +510,10 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran log.Warn("Skipping account, transaction with conditional rate limited", "sender", from, "hash", ltx.Hash, "err", err) txs.Pop() + case env.rpcCtx != nil && env.rpcCtx.Err() != nil && errors.Is(err, env.rpcCtx.Err()): + log.Warn("Transaction processing aborted due to RPC context error", "err", err) + return errBlockInterruptedByTimeout // RPC timeout. Tx could not be checked. + case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account txs.Shift() diff --git a/params/interop.go b/params/interop.go new file mode 100644 index 0000000000..148ece53fe --- /dev/null +++ b/params/interop.go @@ -0,0 +1,5 @@ +package params + +import "github.com/ethereum/go-ethereum/common" + +var InteropCrossL2InboxAddress = common.HexToAddress("0x4200000000000000000000000000000000000022") From c4929cb094c3acba827c2ffc7fe0ee8f2d91122b Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 17 Sep 2024 22:16:09 -0600 Subject: [PATCH 2/3] interop: lazy-dial the supervisor RPC --- cmd/geth/main.go | 1 - cmd/utils/flags.go | 8 -------- eth/backend.go | 8 +------- eth/ethconfig/config.go | 3 +-- eth/ethconfig/gen_config.go | 12 ++++++------ eth/interop/interop.go | 31 +++++++++++++++++++++++++------ 6 files changed, 33 insertions(+), 30 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 226e3fe86f..3552effacb 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -160,7 +160,6 @@ var ( utils.RollupHistoricalRPCFlag, utils.RollupHistoricalRPCTimeoutFlag, utils.RollupInteropRPCFlag, - utils.RollupInteropRPCTimeoutFlag, utils.RollupDisableTxPoolGossipFlag, utils.RollupComputePendingBlock, utils.RollupHaltOnIncompatibleProtocolVersionFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 88d17831a8..94a8fbbc68 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -955,13 +955,6 @@ var ( Category: flags.RollupCategory, } - RollupInteropRPCTimeoutFlag = &cli.StringFlag{ - Name: "rollup.interoprpctimeout", - Usage: "Timeout for interop RPC dial (experimental).", - Value: "5s", - Category: flags.RollupCategory, - } - RollupDisableTxPoolGossipFlag = &cli.BoolFlag{ Name: "rollup.disabletxpoolgossip", Usage: "Disable transaction pool gossip.", @@ -1982,7 +1975,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(RollupInteropRPCFlag.Name) { cfg.InteropMessageRPC = ctx.String(RollupInteropRPCFlag.Name) } - cfg.InteropMessageRPCTimeout = ctx.Duration(RollupInteropRPCTimeoutFlag.Name) cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupDisableTxPoolGossipFlag.Name) cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name) cfg.RollupHaltOnIncompatibleProtocolVersion = ctx.String(RollupHaltOnIncompatibleProtocolVersionFlag.Name) diff --git a/eth/backend.go b/eth/backend.go index 73324d07bd..f56732bc33 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -342,13 +342,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } if config.InteropMessageRPC != "" { - ctx, cancel := context.WithTimeout(context.Background(), config.InteropMessageRPCTimeout) - client, err := interop.DialClient(ctx, config.InteropMessageRPC) - cancel() - if err != nil { - return nil, fmt.Errorf("failed to dial Interop RPC %q: %w", config.InteropMessageRPC, err) - } - eth.interopRPC = client + eth.interopRPC = interop.NewInteropClient(config.InteropMessageRPC) } // Start the RPC service diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index ce56b10100..dc959505e2 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -191,8 +191,7 @@ type Config struct { RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string - InteropMessageRPC string - InteropMessageRPCTimeout time.Duration + InteropMessageRPC string } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index e573cc583f..cd2c6b32c3 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -67,13 +67,13 @@ func (c Config) MarshalTOML() (interface{}, error) { OverrideOptimismInterop *uint64 `toml:",omitempty"` ApplySuperchainUpgrades bool `toml:",omitempty"` RollupSequencerHTTP string + RollupSequencerEnableTxConditional bool RollupHistoricalRPC string RollupHistoricalRPCTimeout time.Duration RollupDisableTxPoolGossip bool RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string InteropMessageRPC string - InteropMessageRPCTimeout time.Duration } var enc Config enc.Genesis = c.Genesis @@ -126,13 +126,13 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.OverrideOptimismInterop = c.OverrideOptimismInterop enc.ApplySuperchainUpgrades = c.ApplySuperchainUpgrades enc.RollupSequencerHTTP = c.RollupSequencerHTTP + enc.RollupSequencerEnableTxConditional = c.RollupSequencerEnableTxConditional enc.RollupHistoricalRPC = c.RollupHistoricalRPC enc.RollupHistoricalRPCTimeout = c.RollupHistoricalRPCTimeout enc.RollupDisableTxPoolGossip = c.RollupDisableTxPoolGossip enc.RollupDisableTxPoolAdmission = c.RollupDisableTxPoolAdmission enc.RollupHaltOnIncompatibleProtocolVersion = c.RollupHaltOnIncompatibleProtocolVersion enc.InteropMessageRPC = c.InteropMessageRPC - enc.InteropMessageRPCTimeout = c.InteropMessageRPCTimeout return &enc, nil } @@ -189,13 +189,13 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { OverrideOptimismInterop *uint64 `toml:",omitempty"` ApplySuperchainUpgrades *bool `toml:",omitempty"` RollupSequencerHTTP *string + RollupSequencerEnableTxConditional *bool RollupHistoricalRPC *string RollupHistoricalRPCTimeout *time.Duration RollupDisableTxPoolGossip *bool RollupDisableTxPoolAdmission *bool RollupHaltOnIncompatibleProtocolVersion *string InteropMessageRPC *string - InteropMessageRPCTimeout *time.Duration } var dec Config if err := unmarshal(&dec); err != nil { @@ -351,6 +351,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.RollupSequencerHTTP != nil { c.RollupSequencerHTTP = *dec.RollupSequencerHTTP } + if dec.RollupSequencerEnableTxConditional != nil { + c.RollupSequencerEnableTxConditional = *dec.RollupSequencerEnableTxConditional + } if dec.RollupHistoricalRPC != nil { c.RollupHistoricalRPC = *dec.RollupHistoricalRPC } @@ -369,8 +372,5 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.InteropMessageRPC != nil { c.InteropMessageRPC = *dec.InteropMessageRPC } - if dec.InteropMessageRPCTimeout != nil { - c.InteropMessageRPCTimeout = *dec.InteropMessageRPCTimeout - } return nil } diff --git a/eth/interop/interop.go b/eth/interop/interop.go index f0aaf7b8c9..618c4301af 100644 --- a/eth/interop/interop.go +++ b/eth/interop/interop.go @@ -2,23 +2,42 @@ package interop import ( "context" + "sync" "github.com/ethereum/go-ethereum/core/types/interoptypes" "github.com/ethereum/go-ethereum/rpc" ) type InteropClient struct { - rpcClient *rpc.Client + mu sync.Mutex + client *rpc.Client + endpoint string } -func DialClient(ctx context.Context, rpcEndpoint string) (*InteropClient, error) { - cl, err := rpc.DialContext(ctx, rpcEndpoint) +// maybeDial dials the endpoint if it was not already. +func (cl *InteropClient) maybeDial(ctx context.Context) error { + cl.mu.Lock() + defer cl.mu.Unlock() + if cl.client != nil { + return nil + } + rpcClient, err := rpc.DialContext(ctx, cl.endpoint) if err != nil { - return nil, err + return err } - return &InteropClient{rpcClient: cl}, nil + cl.client = rpcClient + return nil +} + +func NewInteropClient(rpcEndpoint string) *InteropClient { + return &InteropClient{endpoint: rpcEndpoint} } +// CheckMessages checks if the given messages meet the given minimum safety level. func (cl *InteropClient) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error { - return cl.rpcClient.CallContext(ctx, nil, "supervisor_checkMessages", messages, minSafety) + // we lazy-dial the endpoint, so we can start geth, and build blocks, without supervisor endpoint availability. + if err := cl.maybeDial(ctx); err != nil { // a single dial attempt is made, the next call may retry. + return err + } + return cl.client.CallContext(ctx, nil, "supervisor_checkMessages", messages, minSafety) } From ae9e867d932d3470f54026d03c5a0097f7028106 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 17 Sep 2024 23:31:40 -0600 Subject: [PATCH 3/3] eth: close interop client on shutdown --- eth/backend.go | 3 +++ eth/interop/interop.go | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index f56732bc33..ec84c2b7c9 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -484,6 +484,9 @@ func (s *Ethereum) Stop() error { if s.historicalRPCService != nil { s.historicalRPCService.Close() } + if s.interopRPC != nil { + s.interopRPC.Close() + } // Clean shutdown marker as the last thing before closing db s.shutdownTracker.Stop() diff --git a/eth/interop/interop.go b/eth/interop/interop.go index 618c4301af..bbd09756ca 100644 --- a/eth/interop/interop.go +++ b/eth/interop/interop.go @@ -12,13 +12,14 @@ type InteropClient struct { mu sync.Mutex client *rpc.Client endpoint string + closed bool // don't allow lazy-dials after Close } // maybeDial dials the endpoint if it was not already. func (cl *InteropClient) maybeDial(ctx context.Context) error { cl.mu.Lock() defer cl.mu.Unlock() - if cl.client != nil { + if cl.client != nil || cl.closed { return nil } rpcClient, err := rpc.DialContext(ctx, cl.endpoint) @@ -29,6 +30,15 @@ func (cl *InteropClient) maybeDial(ctx context.Context) error { return nil } +func (cl *InteropClient) Close() { + cl.mu.Lock() + defer cl.mu.Unlock() + if cl.client != nil { + cl.Close() + } + cl.closed = true +} + func NewInteropClient(rpcEndpoint string) *InteropClient { return &InteropClient{endpoint: rpcEndpoint} }