diff --git a/op-node/rollup/engine/build_cancel.go b/op-node/rollup/engine/build_cancel.go index 7c9995c28e85..1a7f737512f0 100644 --- a/op-node/rollup/engine/build_cancel.go +++ b/op-node/rollup/engine/build_cancel.go @@ -21,7 +21,7 @@ func (eq *EngDeriver) onBuildCancel(ev BuildCancelEvent) { defer cancel() // the building job gets wrapped up as soon as the payload is retrieved, there's no explicit cancel in the Engine API eq.log.Warn("cancelling old block building job", "info", ev.Info) - _, err := eq.ec.engine.GetPayload(ctx, ev.Info) + _, err := eq.ec.engine.GetMinimizedPayload(ctx, ev.Info) if err != nil { if x, ok := err.(eth.InputError); ok && x.Code == eth.UnknownPayload { //nolint:all return // if unknown, then it did not need to be cancelled anymore. diff --git a/op-node/rollup/engine/build_seal.go b/op-node/rollup/engine/build_seal.go index a5c72c74fdb7..8c7580932e1b 100644 --- a/op-node/rollup/engine/build_seal.go +++ b/op-node/rollup/engine/build_seal.go @@ -56,7 +56,7 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { defer cancel() sealingStart := time.Now() - envelope, err := eq.ec.engine.GetPayload(ctx, ev.Info) + envelope, err := eq.ec.engine.GetMinimizedPayload(ctx, ev.Info) if err != nil { if x, ok := err.(eth.InputError); ok && x.Code == eth.UnknownPayload { //nolint:all eq.log.Warn("Cannot seal block, payload ID is unknown", @@ -77,16 +77,17 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { return } - if err := sanityCheckPayload(envelope.ExecutionPayload); err != nil { - eq.emitter.Emit(PayloadSealInvalidEvent{ - Info: ev.Info, - Err: fmt.Errorf("failed sanity-check of execution payload contents (ID: %s, blockhash: %s): %w", - ev.Info.ID, envelope.ExecutionPayload.BlockHash, err), - IsLastInSpan: ev.IsLastInSpan, - DerivedFrom: ev.DerivedFrom, - }) - return - } + //Temporarily bypass sanity check to avoid processing all txns + // if err := sanityCheckPayload(envelope.ExecutionPayload); err != nil { + // eq.emitter.Emit(PayloadSealInvalidEvent{ + // Info: ev.Info, + // Err: fmt.Errorf("failed sanity-check of execution payload contents (ID: %s, blockhash: %s): %w", + // ev.Info.ID, envelope.ExecutionPayload.BlockHash, err), + // IsLastInSpan: ev.IsLastInSpan, + // DerivedFrom: ev.DerivedFrom, + // }) + // return + // } ref, err := derive.PayloadToBlockRef(eq.cfg, envelope.ExecutionPayload) if err != nil { diff --git a/op-node/rollup/engine/build_sealed.go b/op-node/rollup/engine/build_sealed.go index d588d77b7f22..d03fc3c94eef 100644 --- a/op-node/rollup/engine/build_sealed.go +++ b/op-node/rollup/engine/build_sealed.go @@ -27,6 +27,7 @@ func (eq *EngDeriver) onBuildSealed(ev BuildSealedEvent) { eq.emitter.Emit(PayloadProcessEvent{ IsLastInSpan: ev.IsLastInSpan, DerivedFrom: ev.DerivedFrom, + Info: ev.Info, Envelope: ev.Envelope, Ref: ev.Ref, }) diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index d8db9cad949c..8c57fad2ecab 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -37,8 +37,10 @@ var ErrNoFCUNeeded = errors.New("no FCU call was needed") type ExecEngine interface { GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) + GetMinimizedPayload(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) + NewPayloadWithPayloadId(ctx context.Context, payloadInfo eth.PayloadInfo, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) } diff --git a/op-node/rollup/engine/payload_process.go b/op-node/rollup/engine/payload_process.go index 4102287f3d23..74a9ea3ae972 100644 --- a/op-node/rollup/engine/payload_process.go +++ b/op-node/rollup/engine/payload_process.go @@ -14,6 +14,8 @@ type PayloadProcessEvent struct { // payload is promoted to pending-safe if non-zero DerivedFrom eth.L1BlockRef + Info eth.PayloadInfo + Envelope *eth.ExecutionPayloadEnvelope Ref eth.L2BlockRef } @@ -25,9 +27,9 @@ func (ev PayloadProcessEvent) String() string { func (eq *EngDeriver) onPayloadProcess(ev PayloadProcessEvent) { ctx, cancel := context.WithTimeout(eq.ctx, payloadProcessTimeout) defer cancel() - - status, err := eq.ec.engine.NewPayload(ctx, - ev.Envelope.ExecutionPayload, ev.Envelope.ParentBeaconBlockRoot) + eq.log.Debug("payload-process, NewPayloadWithPayloadId, payload info:", "info", ev.Info) + status, err := eq.ec.engine.NewPayloadWithPayloadId(ctx, + ev.Info, ev.Envelope.ParentBeaconBlockRoot) if err != nil { eq.emitter.Emit(rollup.EngineTemporaryErrorEvent{ Err: fmt.Errorf("failed to insert execution payload: %w", err)}) diff --git a/op-node/rollup/engine/payload_success.go b/op-node/rollup/engine/payload_success.go index cdd2ee2d030b..dd77cd22db30 100644 --- a/op-node/rollup/engine/payload_success.go +++ b/op-node/rollup/engine/payload_success.go @@ -10,6 +10,8 @@ type PayloadSuccessEvent struct { // payload is promoted to pending-safe if non-zero DerivedFrom eth.L1BlockRef + Info eth.PayloadInfo + Envelope *eth.ExecutionPayloadEnvelope Ref eth.L2BlockRef } diff --git a/op-node/rollup/sequencing/sequencer.go b/op-node/rollup/sequencing/sequencer.go index e816f41a31fa..c66d940bf437 100644 --- a/op-node/rollup/sequencing/sequencer.go +++ b/op-node/rollup/sequencing/sequencer.go @@ -276,23 +276,25 @@ func (d *Sequencer) onBuildSealed(x engine.BuildSealedEvent) { "txs", len(x.Envelope.ExecutionPayload.Transactions), "time", uint64(x.Envelope.ExecutionPayload.Timestamp)) + //TODO: Use GetPayload instead of GetMinimizedPayload if we need to commit an unsafe payload to other conductors. // generous timeout, the conductor is important - ctx, cancel := context.WithTimeout(d.ctx, time.Second*30) - defer cancel() - if err := d.conductor.CommitUnsafePayload(ctx, x.Envelope); err != nil { - d.emitter.Emit(rollup.EngineTemporaryErrorEvent{ - Err: fmt.Errorf("failed to commit unsafe payload to conductor: %w", err)}) - return - } + // ctx, cancel := context.WithTimeout(d.ctx, time.Second*30) + // defer cancel() + // if err := d.conductor.CommitUnsafePayload(ctx, x.Envelope); err != nil { + // d.emitter.Emit(rollup.EngineTemporaryErrorEvent{ + // Err: fmt.Errorf("failed to commit unsafe payload to conductor: %w", err)}) + // return + // } // begin gossiping as soon as possible // asyncGossip.Clear() will be called later if an non-temporary error is found, // or if the payload is successfully inserted - d.asyncGossip.Gossip(x.Envelope) + //d.asyncGossip.Gossip(x.Envelope) // Now after having gossiped the block, try to put it in our own canonical chain d.emitter.Emit(engine.PayloadProcessEvent{ IsLastInSpan: x.IsLastInSpan, DerivedFrom: x.DerivedFrom, + Info: x.Info, Envelope: x.Envelope, Ref: x.Ref, }) diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index fec118567c56..0c4c82947510 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -493,6 +493,16 @@ func (c *Config) NewPayloadVersion(timestamp uint64) eth.EngineAPIMethod { } } +// NewPayloadByIdVersion returns the EngineAPIMethod suitable for the chain hard fork version. +func (c *Config) NewPayloadByIdVersion(timestamp uint64) eth.EngineAPIMethod { + if c.IsEcotone(timestamp) { + // Cancun + return eth.NewPayloadV3ById + } else { + panic("Unsupported Engine API version") + } +} + // GetPayloadVersion returns the EngineAPIMethod suitable for the chain hard fork version. func (c *Config) GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod { if c.IsEcotone(timestamp) { @@ -503,6 +513,16 @@ func (c *Config) GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod { } } +// GetMinimizedPayloadVersion returns the EngineAPIMethod suitable for the chain hard fork version. +func (c *Config) GetMinimizedPayloadVersion(timestamp uint64) eth.EngineAPIMethod { + if c.IsEcotone(timestamp) { + // Cancun + return eth.GetMinimizedPayloadV3 + } else { + return eth.GetPayloadV2 + } +} + // GetOPAltDAConfig validates and returns the altDA config from the rollup config. func (c *Config) GetOPAltDAConfig() (altda.Config, error) { if c.AltDAConfig == nil { diff --git a/op-service/eth/types.go b/op-service/eth/types.go index 5ce11d4dd5b0..97b9ae0ed70f 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -517,6 +517,9 @@ const ( NewPayloadV2 EngineAPIMethod = "engine_newPayloadV2" NewPayloadV3 EngineAPIMethod = "engine_newPayloadV3" + NewPayloadV3ById EngineAPIMethod = "engine_newPayloadV3ById" + GetMinimizedPayloadV3 EngineAPIMethod = "engine_getMinimizedPayloadV3" + GetPayloadV2 EngineAPIMethod = "engine_getPayloadV2" GetPayloadV3 EngineAPIMethod = "engine_getPayloadV3" ) diff --git a/op-service/sources/engine_client.go b/op-service/sources/engine_client.go index 07d7ecb9d271..1ae4fc455258 100644 --- a/op-service/sources/engine_client.go +++ b/op-service/sources/engine_client.go @@ -58,7 +58,9 @@ type EngineAPIClient struct { type EngineVersionProvider interface { ForkchoiceUpdatedVersion(attr *eth.PayloadAttributes) eth.EngineAPIMethod NewPayloadVersion(timestamp uint64) eth.EngineAPIMethod + NewPayloadByIdVersion(timestamp uint64) eth.EngineAPIMethod GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod + GetMinimizedPayloadVersion(timestamp uint64) eth.EngineAPIMethod } func NewEngineAPIClient(rpc client.RPC, l log.Logger, evp EngineVersionProvider) *EngineAPIClient { @@ -142,6 +144,28 @@ func (s *EngineAPIClient) NewPayload(ctx context.Context, payload *eth.Execution return &result, nil } +// NewPayload executes a full block on the execution engine. +// This returns a PayloadStatusV1 which encodes any validation/processing error, +// and this type of error is kept separate from the returned `error` used for RPC errors, like timeouts. +func (s *EngineAPIClient) NewPayloadWithPayloadId(ctx context.Context, payloadInfo eth.PayloadInfo, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) { + e := s.log.New("engine_newPayloadV3ById, payload_id:", payloadInfo.ID) + e.Trace("sending payload id for execution") + + execCtx, cancel := context.WithTimeout(ctx, time.Second*5) + defer cancel() + var result eth.PayloadStatusV1 + + method := s.evp.NewPayloadByIdVersion(payloadInfo.Timestamp) + var err = s.RPC.CallContext(execCtx, &result, string(method), payloadInfo.ID) + + e.Trace("Received payload execution result", "status", result.Status, "latestValidHash", result.LatestValidHash, "message", result.ValidationError) + if err != nil { + e.Error("Payload execution failed", "err", err) + return nil, fmt.Errorf("failed to execute payload: %w", err) + } + return &result, nil +} + // GetPayload gets the execution payload associated with the PayloadId. // There may be two types of error: // 1. `error` as eth.InputError: the payload ID may be unknown @@ -168,7 +192,37 @@ func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.Payloa } return nil, err } - e.Trace("Received payload") + e.Trace("Received payload", string(method), *result.ExecutionPayload) + return &result, nil +} + +// GetMinimizedPayload gets the execution payload associated with the PayloadId while pruning the body (i.e., transactions) of the payload except for the first trasaction. +// There may be two types of error: +// 1. `error` as eth.InputError: the payload ID may be unknown +// 2. Other types of `error`: temporary RPC errors, like timeouts. +func (s *EngineAPIClient) GetMinimizedPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) { + e := s.log.New("payload_id", payloadInfo.ID) + e.Trace("getting minimized payload") + var result eth.ExecutionPayloadEnvelope + method := s.evp.GetMinimizedPayloadVersion(payloadInfo.Timestamp) + err := s.RPC.CallContext(ctx, &result, string(method), payloadInfo.ID) + if err != nil { + e.Warn("Failed to get minimized 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, + } + default: + return nil, fmt.Errorf("unrecognized rpc error: %w", err) + } + } + return nil, err + } + e.Trace("Received payload", string(eth.GetMinimizedPayloadV3), *result.ExecutionPayload) return &result, nil } diff --git a/op-wheel/engine/version_provider.go b/op-wheel/engine/version_provider.go index d3aaa377e1c4..c937239c6d52 100644 --- a/op-wheel/engine/version_provider.go +++ b/op-wheel/engine/version_provider.go @@ -32,6 +32,17 @@ func (v StaticVersionProvider) NewPayloadVersion(uint64) eth.EngineAPIMethod { } } +func (v StaticVersionProvider) NewPayloadByIdVersion(uint64) eth.EngineAPIMethod { + switch int(v) { + case 1, 2: + panic("Unsupported Engine API version: " + strconv.Itoa(int(v))) + case 3: + return eth.NewPayloadV3 + default: + panic("invalid Engine API version: " + strconv.Itoa(int(v))) + } +} + func (v StaticVersionProvider) GetPayloadVersion(uint64) eth.EngineAPIMethod { switch int(v) { case 1, 2: @@ -42,3 +53,14 @@ func (v StaticVersionProvider) GetPayloadVersion(uint64) eth.EngineAPIMethod { panic("invalid Engine API version: " + strconv.Itoa(int(v))) } } + +func (v StaticVersionProvider) GetMinimizedPayloadVersion(uint64) eth.EngineAPIMethod { + switch int(v) { + case 1, 2: + return eth.GetPayloadV2 + case 3: + return eth.GetMinimizedPayloadV3 + default: + panic("invalid Engine API version: " + strconv.Itoa(int(v))) + } +}