Commit f7920585 authored by Sebastian Stammler's avatar Sebastian Stammler Committed by GitHub

op-wheel: Support for Engine API V3 calls & version selection (#9681)

* op-service: split EngineAPIClient out of EngineClient

This way, the Engine API RPC client can be reused in other components, like op-wheel.

* op-service/eth: Add BlockAsPayloadEnv

* op-wheel: Use new Engine API V3 calls

* op-wheel: Add rewind command

Also adds an open RCP endpoint because the "debug" rpc namespace is not
available on the authenticated endpoint.

This also fixes chain config loading.

Also sets some sane default http RPC endpoints.

* op-wheel: Move engine.version validation into flag Action

* op-wheel: improve rewind command
parent 89747141
......@@ -94,14 +94,7 @@ func (o *OracleEngine) PayloadByHash(ctx context.Context, hash common.Hash) (*et
if block == nil {
return nil, ErrNotFound
}
payload, err := eth.BlockAsPayload(block, o.rollupCfg.CanyonTime)
if err != nil {
return nil, err
}
return &eth.ExecutionPayloadEnvelope{
ParentBeaconBlockRoot: block.BeaconRoot(),
ExecutionPayload: payload,
}, nil
return eth.BlockAsPayloadEnv(block, o.rollupCfg.CanyonTime)
}
func (o *OracleEngine) PayloadByNumber(ctx context.Context, n uint64) (*eth.ExecutionPayloadEnvelope, error) {
......
......@@ -316,12 +316,7 @@ func (ea *L2EngineAPI) getPayload(ctx context.Context, payloadId eth.PayloadID)
return nil, engine.UnknownPayload
}
payload, err := eth.BlockAsPayload(bl, ea.config().CanyonTime)
if err != nil {
return nil, err
}
return &eth.ExecutionPayloadEnvelope{ExecutionPayload: payload, ParentBeaconBlockRoot: bl.BeaconRoot()}, nil
return eth.BlockAsPayloadEnv(bl, ea.config().CanyonTime)
}
func (ea *L2EngineAPI) forkchoiceUpdated(ctx context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
......
......@@ -152,11 +152,13 @@ type Uint256Quantity = hexutil.U256
type Data = hexutil.Bytes
type PayloadID = engine.PayloadID
type PayloadInfo struct {
ID PayloadID
Timestamp uint64
}
type (
PayloadID = engine.PayloadID
PayloadInfo struct {
ID PayloadID
Timestamp uint64
}
)
type ExecutionPayloadEnvelope struct {
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"`
......@@ -287,6 +289,17 @@ func BlockAsPayload(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayload,
return payload, nil
}
func BlockAsPayloadEnv(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayloadEnvelope, error) {
payload, err := BlockAsPayload(bl, canyonForkTime)
if err != nil {
return nil, err
}
return &ExecutionPayloadEnvelope{
ExecutionPayload: payload,
ParentBeaconBlockRoot: bl.BeaconRoot(),
}, nil
}
type PayloadAttributes struct {
// value for the timestamp field of the new payload
Timestamp Uint64Quantity `json:"timestamp"`
......@@ -296,14 +309,17 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient"`
// Withdrawals to include into the block -- should be nil or empty depending on Shanghai enablement
Withdrawals *types.Withdrawals `json:"withdrawals,omitempty"`
// parentBeaconBlockRoot optional extension in Dencun
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"`
// Optimism additions
// Transactions to force into the block (always at the start of the transactions list).
Transactions []Data `json:"transactions,omitempty"`
// NoTxPool to disable adding any transactions from the transaction-pool.
NoTxPool bool `json:"noTxPool,omitempty"`
// GasLimit override
GasLimit *Uint64Quantity `json:"gasLimit,omitempty"`
// parentBeaconBlockRoot optional extension in Dencun
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"`
}
type ExecutePayloadStatus string
......
......@@ -31,6 +31,7 @@ func EngineClientDefaultConfig(config *rollup.Config) *EngineClientConfig {
// EngineClient extends L2Client with engine API bindings.
type EngineClient struct {
*L2Client
*EngineAPIClient
}
func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics, config *EngineClientConfig) (*EngineClient, error) {
......@@ -39,11 +40,39 @@ func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics,
return nil, err
}
engineAPIClient := NewEngineAPIClient(client, log, config.RollupCfg)
return &EngineClient{
L2Client: l2Client,
L2Client: l2Client,
EngineAPIClient: engineAPIClient,
}, nil
}
// EngineAPIClient is an RPC client for the Engine API functions.
type EngineAPIClient struct {
RPC client.RPC
log log.Logger
evp EngineVersionProvider
}
type EngineVersionProvider interface {
ForkchoiceUpdatedVersion(attr *eth.PayloadAttributes) eth.EngineAPIMethod
NewPayloadVersion(timestamp uint64) eth.EngineAPIMethod
GetPayloadVersion(timestamp uint64) eth.EngineAPIMethod
}
func NewEngineAPIClient(rpc client.RPC, l log.Logger, evp EngineVersionProvider) *EngineAPIClient {
return &EngineAPIClient{
RPC: rpc,
log: l,
evp: evp,
}
}
// EngineVersionProvider returns the underlying engine version provider used for
// resolving the correct Engine API versions.
func (s *EngineAPIClient) EngineVersionProvider() EngineVersionProvider { return s.evp }
// ForkchoiceUpdate updates the forkchoice on the execution client. If attributes is not nil, the engine client will also begin building a block
// based on attributes after the new head block and return the payload ID.
//
......@@ -51,15 +80,15 @@ func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics,
// 1. Processing error: ForkchoiceUpdatedResult.PayloadStatusV1.ValidationError or other non-success PayloadStatusV1,
// 2. `error` as eth.InputError: the forkchoice state or attributes are not valid.
// 3. Other types of `error`: temporary RPC errors, like timeouts.
func (s *EngineClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
func (s *EngineAPIClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
llog := s.log.New("state", fc) // local logger
tlog := llog.New("attr", attributes) // trace logger
tlog.Trace("Sharing forkchoice-updated signal")
fcCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
var result eth.ForkchoiceUpdatedResult
method := s.rollupCfg.ForkchoiceUpdatedVersion(attributes)
err := s.client.CallContext(fcCtx, &result, string(method), fc, attributes)
method := s.evp.ForkchoiceUpdatedVersion(attributes)
err := s.RPC.CallContext(fcCtx, &result, string(method), fc, attributes)
if err == nil {
tlog.Trace("Shared forkchoice-updated signal")
if attributes != nil { // block building is optional, we only get a payload ID if we are building a block
......@@ -87,7 +116,7 @@ func (s *EngineClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceS
// 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 *EngineClient) NewPayload(ctx context.Context, payload *eth.ExecutionPayload, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) {
func (s *EngineAPIClient) NewPayload(ctx context.Context, payload *eth.ExecutionPayload, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) {
e := s.log.New("block_hash", payload.BlockHash)
e.Trace("sending payload for execution")
......@@ -96,11 +125,11 @@ func (s *EngineClient) NewPayload(ctx context.Context, payload *eth.ExecutionPay
var result eth.PayloadStatusV1
var err error
switch method := s.rollupCfg.NewPayloadVersion(uint64(payload.Timestamp)); method {
switch method := s.evp.NewPayloadVersion(uint64(payload.Timestamp)); method {
case eth.NewPayloadV3:
err = s.client.CallContext(execCtx, &result, string(method), payload, []common.Hash{}, parentBeaconBlockRoot)
err = s.RPC.CallContext(execCtx, &result, string(method), payload, []common.Hash{}, parentBeaconBlockRoot)
case eth.NewPayloadV2:
err = s.client.CallContext(execCtx, &result, string(method), payload)
err = s.RPC.CallContext(execCtx, &result, string(method), payload)
default:
return nil, fmt.Errorf("unsupported NewPayload version: %s", method)
}
......@@ -117,12 +146,12 @@ func (s *EngineClient) NewPayload(ctx context.Context, payload *eth.ExecutionPay
// 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 *EngineClient) GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) {
func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) {
e := s.log.New("payload_id", payloadInfo.ID)
e.Trace("getting payload")
var result eth.ExecutionPayloadEnvelope
method := s.rollupCfg.GetPayloadVersion(payloadInfo.Timestamp)
err := s.client.CallContext(ctx, &result, string(method), payloadInfo.ID)
method := s.evp.GetPayloadVersion(payloadInfo.Timestamp)
err := s.RPC.CallContext(ctx, &result, string(method), payloadInfo.ID)
if err != nil {
e.Warn("Failed to get payload", "payload_id", payloadInfo.ID, "err", err)
if rpcErr, ok := err.(rpc.Error); ok {
......@@ -143,9 +172,9 @@ func (s *EngineClient) GetPayload(ctx context.Context, payloadInfo eth.PayloadIn
return &result, nil
}
func (s *EngineClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) {
func (s *EngineAPIClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) {
var result params.ProtocolVersion
err := s.client.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{
err := s.RPC.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{
Recommended: recommended,
Required: required,
})
......
This diff is collapsed.
This diff is collapsed.
package engine
import (
"strconv"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
type StaticVersionProvider int
func (v StaticVersionProvider) ForkchoiceUpdatedVersion(*eth.PayloadAttributes) eth.EngineAPIMethod {
switch int(v) {
case 1:
return eth.FCUV1
case 2:
return eth.FCUV2
case 3:
return eth.FCUV3
default:
panic("invalid Engine API version: " + strconv.Itoa(int(v)))
}
}
func (v StaticVersionProvider) NewPayloadVersion(uint64) eth.EngineAPIMethod {
switch int(v) {
case 1, 2:
return eth.NewPayloadV2
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:
return eth.GetPayloadV2
case 3:
return eth.GetPayloadV3
default:
panic("invalid Engine API version: " + strconv.Itoa(int(v)))
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment