From 769e7c802ba5039974cee8815272e20300353c8c Mon Sep 17 00:00:00 2001 From: bnoieh <135800952+bnoieh@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:48:28 +0800 Subject: [PATCH] feat: support combined engine api --- op-node/flags/flags.go | 7 +++ op-node/rollup/derive/engine_controller.go | 14 +++++- op-node/rollup/derive/engine_update.go | 55 ++++++++++++++++++++++ op-node/rollup/driver/config.go | 2 + op-node/rollup/driver/driver.go | 2 +- op-node/service.go | 13 ++--- op-service/eth/types.go | 6 +++ op-service/sources/engine_client.go | 33 ++++++++++++- 8 files changed, 122 insertions(+), 10 deletions(-) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 03c48f0f3e..811e0fba25 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -251,6 +251,12 @@ var ( EnvVars: prefixEnvVars("SEQUENCER_PRIORITY"), Category: SequencerCategory, } + SequencerCombinedEngineFlag = &cli.BoolFlag{ + Name: "sequencer.combined-engine", + Usage: "Enable sequencer select combined engine api when sealing payload.", + EnvVars: prefixEnvVars("SEQUENCER_COMBINED_ENGINE"), + Category: SequencerCategory, + } SequencerL1Confs = &cli.Uint64Flag{ Name: "sequencer.l1-confs", Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.", @@ -437,6 +443,7 @@ var optionalFlags = []cli.Flag{ SequencerStoppedFlag, SequencerMaxSafeLagFlag, SequencerPriorityFlag, + SequencerCombinedEngineFlag, SequencerL1Confs, L1EpochPollIntervalFlag, RuntimeConfigReloadIntervalFlag, diff --git a/op-node/rollup/derive/engine_controller.go b/op-node/rollup/derive/engine_controller.go index f53ad70658..96469ceb6d 100644 --- a/op-node/rollup/derive/engine_controller.go +++ b/op-node/rollup/derive/engine_controller.go @@ -50,6 +50,7 @@ type ExecEngine interface { GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) ForkchoiceUpdate(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) NewPayload(ctx context.Context, payload *eth.ExecutionPayload, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) + SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState) (*eth.SealPayloadResponse, error) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) } @@ -84,9 +85,11 @@ type EngineController struct { buildingInfo eth.PayloadInfo buildingSafe bool safeAttrs *AttributesWithParent + + combinedAPI bool } -func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rollupCfg *rollup.Config, syncConfig *sync.Config) *EngineController { +func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rollupCfg *rollup.Config, syncConfig *sync.Config, combinedAPI bool) *EngineController { syncStatus := syncStatusCL if syncConfig.SyncMode == sync.ELSync { syncStatus = syncStatusWillStartEL @@ -102,6 +105,7 @@ func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rol elTriggerGap: syncConfig.ELTriggerGap, syncStatus: syncStatus, clock: clock.SystemClock, + combinedAPI: combinedAPI, } } @@ -267,7 +271,13 @@ func (e *EngineController) ConfirmPayload(ctx context.Context, agossip async.Asy } // Update the safe head if the payload is built with the last attributes in the batch. updateSafe := e.buildingSafe && e.safeAttrs != nil && e.safeAttrs.IsLastInSpan - envelope, errTyp, err := confirmPayload(ctx, e.log, e.engine, fc, e.buildingInfo, updateSafe, agossip, sequencerConductor, e.metrics) + + var envelope *eth.ExecutionPayloadEnvelope + if e.combinedAPI { + envelope, errTyp, err = confirmPayloadCombined(ctx, e.log, e.engine, fc, e.buildingInfo, updateSafe, agossip, sequencerConductor, e.metrics) + } else { + envelope, errTyp, err = confirmPayload(ctx, e.log, e.engine, fc, e.buildingInfo, updateSafe, agossip, sequencerConductor, e.metrics) + } if err != nil { return nil, errTyp, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", e.buildingOnto, e.buildingInfo.ID, errTyp, err) } diff --git a/op-node/rollup/derive/engine_update.go b/op-node/rollup/derive/engine_update.go index d3e3c8bf19..21373013b8 100644 --- a/op-node/rollup/derive/engine_update.go +++ b/op-node/rollup/derive/engine_update.go @@ -208,3 +208,58 @@ func confirmPayload( "txs", len(payload.Transactions), "update_safe", updateSafe) return envelope, BlockInsertOK, nil } + +func confirmPayloadCombined( + ctx context.Context, + log log.Logger, + eng ExecEngine, + fc eth.ForkchoiceState, + payloadInfo eth.PayloadInfo, + updateSafe bool, + agossip async.AsyncGossiper, + sequencerConductor conductor.SequencerConductor, + metrics Metrics, +) (out *eth.ExecutionPayloadEnvelope, errTyp BlockInsertionErrType, err error) { + start := time.Now() + sealRes, err := eng.SealPayload(ctx, payloadInfo, &fc) + + if err != nil { + var inputErr eth.InputError + if errors.As(err, &inputErr) { + switch inputErr.Code { + case eth.InvalidForkchoiceState: + return nil, BlockInsertPayloadErr, fmt.Errorf("post-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap()) + default: + return nil, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err) + } + } else { + return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to make the new L2 block canonical via forkchoice: %w", err) + } + } + + if sealRes.PayloadStatus.Status != eth.ExecutionValid { + switch sealRes.Stage { + case "newPayload": + if sealRes.PayloadStatus.Status == eth.ExecutionInvalid || sealRes.PayloadStatus.Status == eth.ExecutionInvalidBlockHash { + return nil, BlockInsertPayloadErr, fmt.Errorf("new payload BlockInsertPayloadErr %v", sealRes.PayloadStatus.ValidationError) + } + return nil, BlockInsertTemporaryErr, fmt.Errorf("new payload BlockInsertTemporaryErr %v", sealRes.PayloadStatus.ValidationError) + case "forkchoiceUpdate": + return nil, BlockInsertPayloadErr, fmt.Errorf("forkchoiceUpdate BlockInsertPayloadErr %v", sealRes.PayloadStatus.ValidationError) + } + } + + if err := sequencerConductor.CommitUnsafePayload(ctx, sealRes.Payload); err != nil { + return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to commit unsafe payload to conductor: %w", err) + } + agossip.Gossip(sealRes.Payload) + agossip.Clear() + + payload := sealRes.Payload.ExecutionPayload + metrics.RecordSequencerStepTime("sealPayload", time.Since(start)) + log.Info("inserted block", "hash", payload.BlockHash, "number", uint64(payload.BlockNumber), + "state_root", payload.StateRoot, "timestamp", uint64(payload.Timestamp), "parent", payload.ParentHash, + "prev_randao", payload.PrevRandao, "fee_recipient", payload.FeeRecipient, + "txs", len(payload.Transactions), "update_safe", updateSafe) + return sealRes.Payload, BlockInsertOK, nil +} diff --git a/op-node/rollup/driver/config.go b/op-node/rollup/driver/config.go index fa0d6932ca..42ebe6f635 100644 --- a/op-node/rollup/driver/config.go +++ b/op-node/rollup/driver/config.go @@ -23,4 +23,6 @@ type Config struct { // SequencerPriority is true when sequencer step takes precedence over other steps. SequencerPriority bool `json:"sequencer_priority"` + + SequencerCombinedEngine bool `json:"sequencer_combined_engine"` } diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index f7f610f4fa..7b127eccb5 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -157,7 +157,7 @@ func NewDriver( sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1) - engine := derive.NewEngineController(l2, log, metrics, cfg, syncCfg) + engine := derive.NewEngineController(l2, log, metrics, cfg, syncCfg, driverCfg.SequencerCombinedEngine) clSync := clsync.NewCLSync(log, cfg, metrics, engine) var finalizer Finalizer diff --git a/op-node/service.go b/op-node/service.go index c1fd6f8abf..134a916ee0 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -204,12 +204,13 @@ func NewConfigPersistence(ctx *cli.Context) node.ConfigPersistence { func NewDriverConfig(ctx *cli.Context) *driver.Config { return &driver.Config{ - VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name), - SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name), - SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name), - SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name), - SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name), - SequencerPriority: ctx.Bool(flags.SequencerPriorityFlag.Name), + VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name), + SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name), + SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name), + SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name), + SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name), + SequencerPriority: ctx.Bool(flags.SequencerPriorityFlag.Name), + SequencerCombinedEngine: ctx.Bool(flags.SequencerCombinedEngineFlag.Name), } } diff --git a/op-service/eth/types.go b/op-service/eth/types.go index 605c10ffce..5dde9d656f 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -367,6 +367,12 @@ type ForkchoiceUpdatedResult struct { PayloadID *PayloadID `json:"payloadId"` } +type SealPayloadResponse struct { + Stage string `json:"stage"` + PayloadStatus PayloadStatusV1 `json:"payloadStatus"` + Payload *ExecutionPayloadEnvelope `json:"payload"` +} + // SystemConfig represents the rollup system configuration that carries over in every L2 block, // and may be changed through L1 system config events. // The initial SystemConfig at rollup genesis is embedded in the rollup configuration. diff --git a/op-service/sources/engine_client.go b/op-service/sources/engine_client.go index 9490df78f9..68fc6d67cc 100644 --- a/op-service/sources/engine_client.go +++ b/op-service/sources/engine_client.go @@ -139,7 +139,7 @@ func (s *EngineAPIClient) NewPayload(ctx context.Context, payload *eth.Execution e.Trace("Received payload execution result", "status", result.Status, "latestValidHash", result.LatestValidHash, "message", result.ValidationError) if err != nil { if strings.Contains(err.Error(), derive.ErrELSyncTriggerUnexpected.Error()) { - result.Status = eth.ExecutionSyncing + result.Status = eth.ExecutionSyncing // why? return &result, err } e.Error("Payload execution failed", "err", err) @@ -178,6 +178,37 @@ func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.Payloa return &result, nil } +func (s *EngineAPIClient) SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState) (*eth.SealPayloadResponse, error) { + e := s.log.New("payload_id", payloadInfo.ID) + e.Trace("sealing payload") + var result eth.SealPayloadResponse + method := "engine_opSealPayload" + err := s.RPC.CallContext(ctx, &result, string(method), payloadInfo.ID, fc) + if err != nil { + e.Warn("Failed to seal payload", "payload_id", payloadInfo.ID, "err", err) + if rpcErr, ok := err.(rpc.Error); ok { + code := eth.ErrorCode(rpcErr.ErrorCode()) + switch code { + case eth.UnknownPayload: + return nil, eth.InputError{ + Inner: err, + Code: code, + } + case eth.InvalidForkchoiceState, eth.InvalidPayloadAttributes: + return nil, eth.InputError{ + Inner: err, + Code: code, + } + default: + return nil, fmt.Errorf("unrecognized rpc error: %w", err) + } + } + return nil, err + } + e.Trace("Received payload") + return &result, nil +} + func (s *EngineAPIClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) { var result params.ProtocolVersion err := s.RPC.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{