Skip to content

Commit

Permalink
feat: add new engine_opSealPayload API (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
bnoieh authored Dec 16, 2024
1 parent c5d19da commit 9dc6fac
Show file tree
Hide file tree
Showing 16 changed files with 276 additions and 22 deletions.
2 changes: 1 addition & 1 deletion op-e2e/actions/l2_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type safeDB interface {

func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, blobsSrc derive.L1BlobsFetcher, plasmaSrc driver.PlasmaIface, eng L2API, cfg *rollup.Config, syncCfg *sync.Config, safeHeadListener safeDB) *L2Verifier {
metrics := &testutils.TestDerivationMetrics{}
engine := derive.NewEngineController(eng, log, metrics, cfg, syncCfg)
engine := derive.NewEngineController(eng, log, metrics, cfg, syncCfg, false)

clSync := clsync.NewCLSync(log, cfg, metrics, engine)

Expand Down
7 changes: 7 additions & 0 deletions op-node/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -437,6 +443,7 @@ var optionalFlags = []cli.Flag{
SequencerStoppedFlag,
SequencerMaxSafeLagFlag,
SequencerPriorityFlag,
SequencerCombinedEngineFlag,
SequencerL1Confs,
L1EpochPollIntervalFlag,
RuntimeConfigReloadIntervalFlag,
Expand Down
14 changes: 7 additions & 7 deletions op-node/rollup/attributes/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func TestAttributesHandler(t *testing.T) {
t.Run("drop stale attributes", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)
defer eng.AssertExpectations(t)

Expand All @@ -195,7 +195,7 @@ func TestAttributesHandler(t *testing.T) {
t.Run("pending gets reorged", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)
defer eng.AssertExpectations(t)

Expand All @@ -210,7 +210,7 @@ func TestAttributesHandler(t *testing.T) {
t.Run("consolidation fails", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)

ec.SetUnsafeHead(refA1)
Expand Down Expand Up @@ -264,7 +264,7 @@ func TestAttributesHandler(t *testing.T) {
fn := func(t *testing.T, lastInSpan bool) {
logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)

ec.SetUnsafeHead(refA1)
Expand Down Expand Up @@ -323,7 +323,7 @@ func TestAttributesHandler(t *testing.T) {

logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)

ec.SetUnsafeHead(refA0)
Expand Down Expand Up @@ -374,7 +374,7 @@ func TestAttributesHandler(t *testing.T) {

logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)

ec.SetUnsafeHead(refA0)
Expand All @@ -398,7 +398,7 @@ func TestAttributesHandler(t *testing.T) {
t.Run("no attributes", func(t *testing.T) {
logger := testlog.Logger(t, log.LevelInfo)
eng := &testutils.MockEngine{}
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync})
ec := derive.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, &sync.Config{SyncMode: sync.CLSync}, false)
ah := NewAttributesHandler(logger, cfg, ec, eng)
defer eng.AssertExpectations(t)

Expand Down
14 changes: 12 additions & 2 deletions op-node/rollup/derive/engine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, needPayload bool) (*eth.SealPayloadResponse, string, error)
L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error)
}

Expand Down Expand Up @@ -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
Expand All @@ -102,6 +105,7 @@ func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rol
elTriggerGap: syncConfig.ELTriggerGap,
syncStatus: syncStatus,
clock: clock.SystemClock,
combinedAPI: combinedAPI,
}
}

Expand Down Expand Up @@ -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 && !e.buildingSafe {
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)
}
Expand Down
8 changes: 4 additions & 4 deletions op-node/rollup/derive/engine_queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func TestEngineQueue_ResetWhenUnsafeOriginNotCanonical(t *testing.T) {
SyncMode: sync.CLSync,
SkipSyncStartCheck: false,
ELTriggerGap: 0,
})
}, false)
eq := NewEngineQueue(logger, cfg, eng, ec, metrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, &fakeAttributesHandler{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)

Expand Down Expand Up @@ -634,7 +634,7 @@ func TestVerifyNewL1Origin(t *testing.T) {
SyncMode: sync.CLSync,
SkipSyncStartCheck: false,
ELTriggerGap: 0,
})
}, false)
eq := NewEngineQueue(logger, cfg, eng, ec, metrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, &fakeAttributesHandler{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)

Expand Down Expand Up @@ -738,7 +738,7 @@ func TestBlockBuildingRace(t *testing.T) {
SyncMode: sync.CLSync,
SkipSyncStartCheck: false,
ELTriggerGap: 0,
})
}, false)
attribHandler := &fakeAttributesHandler{}
eq := NewEngineQueue(logger, cfg, eng, ec, metrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, attribHandler)
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
Expand Down Expand Up @@ -858,7 +858,7 @@ func TestResetLoop(t *testing.T) {
SyncMode: sync.CLSync,
SkipSyncStartCheck: false,
ELTriggerGap: 0,
})
}, false)
eq := NewEngineQueue(logger, cfg, eng, ec, metrics.NoopMetrics, prev, l1F, &sync.Config{}, safedb.Disabled, noopFinality{}, &fakeAttributesHandler{})
eq.ec.SetUnsafeHead(refA2)
eq.ec.SetSafeHead(refA1)
Expand Down
128 changes: 128 additions & 0 deletions op-node/rollup/derive/engine_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,131 @@ func confirmPayload(
"txs", len(payload.Transactions), "update_safe", updateSafe)
return envelope, BlockInsertOK, nil
}

// confirmPayloadCombined is equal to confirmPayload but using engine_opSealPayload API to combine GetPayload, NewPayload, ForckchoiceUpdated calls
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()
type SealPayloadRet struct {
res *eth.SealPayloadResponse
errStage string
err error
}
sealPayloadRetCh := make(chan SealPayloadRet, 1)
go func() {
res, errStage, err := eng.SealPayload(ctx, payloadInfo, &fc, false)
sealPayloadRetCh <- SealPayloadRet{res, errStage, err}
}()

type GetPayloadRet struct {
res *eth.ExecutionPayloadEnvelope
err error
}
getPayloadRetCh := make(chan GetPayloadRet, 1)
go func() {
res, err := eng.GetPayload(ctx, payloadInfo)
getPayloadRetCh <- GetPayloadRet{res, err}
}()

getPayloadRet := <-getPayloadRetCh
envelope := getPayloadRet.res
getPayloadErr := getPayloadRet.err
validatePayloadErr := error(nil)
if getPayloadErr == nil {
payload := envelope.ExecutionPayload
validatePayloadErr = sanityCheckPayload(payload)
if validatePayloadErr == nil {
// TODO handle sequencerConductor component
if err := sequencerConductor.CommitUnsafePayload(ctx, envelope); err != nil {
log.Error("failed to commit unsafe payload to conductor", "payloadID", payloadInfo.ID, "err", err)
}
agossip.Gossip(envelope)
}
}

sealPayloadRet := <-sealPayloadRetCh
sealRes := sealPayloadRet.res
errStage := sealPayloadRet.errStage
sealPayloadErr := sealPayloadRet.err
switch errStage {
case eth.GetPayloadStage:
return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to get execution payload: %w", sealPayloadErr)
case eth.NewPayloadStage:
if sealPayloadErr != nil {
return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to insert execution payload: %w", sealPayloadErr)
}
validationError := "validation error is nil"
if sealRes.PayloadStatus.ValidationError != nil {
validationError = *sealRes.PayloadStatus.ValidationError
}
if sealRes.PayloadStatus.Status == eth.ExecutionInvalid || sealRes.PayloadStatus.Status == eth.ExecutionInvalidBlockHash {
agossip.Clear()
log.Error("Seal payload failed to new payload", "payloadID", payloadInfo.ID, "status", sealRes.PayloadStatus)
return nil, BlockInsertPayloadErr, fmt.Errorf("failed to new payload, status: %s, validationError: %v", sealRes.PayloadStatus.Status, validationError)
}
if sealRes.PayloadStatus.Status != eth.ExecutionValid {
return nil, BlockInsertTemporaryErr, fmt.Errorf("failed to new payload, status: %s, validationError: %v", sealRes.PayloadStatus.Status, validationError)
}
case eth.ForkchoiceUpdatedStage:
if sealPayloadErr != nil {
var inputErr eth.InputError
if errors.As(sealPayloadErr, &inputErr) {
switch inputErr.Code {
case eth.InvalidForkchoiceState:
// if we succeed to update the forkchoice pre-payload, but fail post-payload, then it is a payload error
agossip.Clear()
return nil, BlockInsertPayloadErr, fmt.Errorf("post-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())
default:
agossip.Clear()
return nil, BlockInsertPrestateErr, fmt.Errorf("unexpected error code in forkchoice-updated response: %w", sealPayloadErr)
}
} else {
agossip.Clear()
return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to make the new L2 block canonical via forkchoice: %w", sealPayloadErr))
}
}
validationError := "validation error is nil"
if sealRes.PayloadStatus.ValidationError != nil {
validationError = *sealRes.PayloadStatus.ValidationError
}
if sealRes.PayloadStatus.Status != eth.ExecutionValid {
agossip.Clear()
return nil, BlockInsertPayloadErr, fmt.Errorf("failed to forkchoice update, status: %s, validationError: %v", sealRes.PayloadStatus.Status, validationError)
}
default:
if sealPayloadErr != nil {
return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to seal payload, err: %w", sealPayloadErr))
}
if sealRes == nil {
return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to seal payload, got empty response"))
}
if sealRes.PayloadStatus.Status != eth.ExecutionValid {
return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to seal payload, status: %s", sealRes.PayloadStatus.Status))
}
}

if getPayloadErr != nil {
return nil, BlockInsertTemporaryErr, NewTemporaryError(fmt.Errorf("failed to get payload: %w", getPayloadErr))
}
if validatePayloadErr != nil {
return nil, BlockInsertPayloadErr, NewCriticalError(fmt.Errorf("failed to validate payload but seal succeed: %w", validatePayloadErr))
}

agossip.Clear()
payload := envelope.ExecutionPayload
metrics.RecordSequencerStepTime("sealPayload", time.Since(start))
log.Info("Sealed block succeed", "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 envelope, BlockInsertOK, nil
}
2 changes: 2 additions & 0 deletions op-node/rollup/driver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
2 changes: 1 addition & 1 deletion op-node/rollup/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions op-node/rollup/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,16 @@ func (c *Config) GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod {
}
}

// SealPayloadVersion returns the EngineAPIMethod suitable for the chain hard fork version.
func (c *Config) SealPayloadVersion(timestamp uint64) eth.EngineAPIMethod {
if c.IsEcotone(timestamp) {
// Cancun
return eth.SealPayloadV3
} else {
return eth.SealPayloadV2
}
}

// GetOPPlasmaConfig validates and returns the plasma config from the rollup config.
func (c *Config) GetOPPlasmaConfig() (plasma.Config, error) {
if c.PlasmaConfig == nil {
Expand Down
13 changes: 7 additions & 6 deletions op-node/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down
2 changes: 1 addition & 1 deletion op-program/client/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher,
SyncMode: sync.CLSync,
SkipSyncStartCheck: false,
ELTriggerGap: 0,
})
}, false)
attributesHandler := attributes.NewAttributesHandler(logger, cfg, engine, l2Source)
pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l1BlobsSource, plasma.Disabled, l2Source, engine, metrics.NoopMetrics, &sync.Config{}, safedb.Disabled, NoopFinalizer{}, attributesHandler)
pipeline.Reset()
Expand Down
4 changes: 4 additions & 0 deletions op-program/client/l2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ func (o *OracleEngine) NewPayload(ctx context.Context, payload *eth.ExecutionPay
}
}

func (o *OracleEngine) SealPayload(ctx context.Context, payloadInfo eth.PayloadInfo, fc *eth.ForkchoiceState, needPayload bool) (*eth.SealPayloadResponse, string, error) {
return nil, "", nil
}

func (o *OracleEngine) PayloadByHash(ctx context.Context, hash common.Hash) (*eth.ExecutionPayloadEnvelope, error) {
block := o.backend.GetBlockByHash(hash)
if block == nil {
Expand Down
Loading

0 comments on commit 9dc6fac

Please sign in to comment.