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

feat: adapt to Engine API changes for GetPayload and NewPayload methods #5

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion op-node/rollup/engine/build_cancel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 12 additions & 11 deletions op-node/rollup/engine/build_seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions op-node/rollup/engine/build_sealed.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
2 changes: 2 additions & 0 deletions op-node/rollup/engine/engine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
8 changes: 5 additions & 3 deletions op-node/rollup/engine/payload_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)})
Expand Down
2 changes: 2 additions & 0 deletions op-node/rollup/engine/payload_success.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
18 changes: 10 additions & 8 deletions op-node/rollup/sequencing/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
20 changes: 20 additions & 0 deletions op-node/rollup/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions op-service/eth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
56 changes: 55 additions & 1 deletion op-service/sources/engine_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down
22 changes: 22 additions & 0 deletions op-wheel/engine/version_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)))
}
}