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,
})
......
......@@ -18,12 +18,16 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-node/rollup"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/client"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-wheel/cheat"
"github.com/ethereum-optimism/optimism/op-wheel/engine"
)
......@@ -50,8 +54,9 @@ var (
}
EngineEndpoint = &cli.StringFlag{
Name: "engine",
Usage: "Engine API RPC endpoint, can be HTTP/WS/IPC",
Usage: "Authenticated Engine API RPC endpoint, can be HTTP/WS/IPC",
Required: true,
Value: "http://localhost:8551/",
EnvVars: prefixEnvVars("ENGINE"),
}
EngineJWTPath = &cli.StringFlag{
......@@ -61,6 +66,23 @@ var (
TakesFile: true,
EnvVars: prefixEnvVars("ENGINE_JWT_SECRET"),
}
EngineOpenEndpoint = &cli.StringFlag{
Name: "engine.open",
Usage: "Open Engine API RPC endpoint, can be HTTP/WS/IPC",
Value: "http://localhost:8545/",
EnvVars: prefixEnvVars("ENGINE_OPEN"),
}
EngineVersion = &cli.IntFlag{
Name: "engine.version",
Usage: "Engine API version to use for Engine calls (1, 2, or 3)",
EnvVars: prefixEnvVars("ENGINE_VERSION"),
Action: func(ctx *cli.Context, ev int) error {
if ev < 1 || ev > 3 {
return fmt.Errorf("invalid Engine API version: %d", ev)
}
return nil
},
}
FeeRecipientFlag = &cli.GenericFlag{
Name: "fee-recipient",
Usage: "fee-recipient of the block building",
......@@ -92,6 +114,12 @@ var (
}
)
func withEngineFlags(flags ...cli.Flag) []cli.Flag {
return append(append(flags,
EngineEndpoint, EngineJWTPath, EngineOpenEndpoint, EngineVersion),
oplog.CLIFlags(envVarPrefix)...)
}
func ParseBuildingArgs(ctx *cli.Context) *engine.BlockBuildingSettings {
return &engine.BlockBuildingSettings{
BlockTime: ctx.Uint64(BlockTimeFlag.Name),
......@@ -124,19 +152,84 @@ func CheatRawDBAction(readOnly bool, fn func(ctx *cli.Context, db ethdb.Database
}
}
func EngineAction(fn func(ctx *cli.Context, client client.RPC) error) cli.ActionFunc {
func EngineAction(fn func(ctx *cli.Context, client *sources.EngineAPIClient, lgr log.Logger) error) cli.ActionFunc {
return func(ctx *cli.Context) error {
jwtData, err := os.ReadFile(ctx.String(EngineJWTPath.Name))
lgr := initLogger(ctx)
rpc, err := initEngineRPC(ctx, lgr)
if err != nil {
return fmt.Errorf("failed to read jwt: %w", err)
return fmt.Errorf("failed to dial Engine API endpoint %q: %w",
ctx.String(EngineEndpoint.Name), err)
}
secret := common.HexToHash(strings.TrimSpace(string(jwtData)))
endpoint := ctx.String(EngineEndpoint.Name)
client, err := engine.DialClient(context.Background(), endpoint, secret)
evp, err := initVersionProvider(ctx, lgr)
if err != nil {
return fmt.Errorf("failed to dial Engine API endpoint %q: %w", endpoint, err)
return fmt.Errorf("failed to init Engine version provider: %w", err)
}
return fn(ctx, client)
client := sources.NewEngineAPIClient(rpc, lgr, evp)
return fn(ctx, client, lgr)
}
}
func initLogger(ctx *cli.Context) log.Logger {
logCfg := oplog.ReadCLIConfig(ctx)
lgr := oplog.NewLogger(oplog.AppOut(ctx), logCfg)
oplog.SetGlobalLogHandler(lgr.Handler())
return lgr
}
func initEngineRPC(ctx *cli.Context, lgr log.Logger) (client.RPC, error) {
jwtData, err := os.ReadFile(ctx.String(EngineJWTPath.Name))
if err != nil {
return nil, fmt.Errorf("failed to read jwt: %w", err)
}
secret := common.HexToHash(strings.TrimSpace(string(jwtData)))
endpoint := ctx.String(EngineEndpoint.Name)
return client.NewRPC(ctx.Context, lgr, endpoint,
client.WithGethRPCOptions(rpc.WithHTTPAuth(node.NewJWTAuth(secret))))
}
func initVersionProvider(ctx *cli.Context, lgr log.Logger) (sources.EngineVersionProvider, error) {
// static configuration takes precedent, if set
if ctx.IsSet(EngineVersion.Name) {
ev := ctx.Int(EngineVersion.Name)
return engine.StaticVersionProvider(ev), nil
}
// otherwise get config from EL
rpc, err := initOpenEngineRPC(ctx, lgr)
if err != nil {
return nil, err
}
cfg, err := engine.GetChainConfig(ctx.Context, rpc)
if err != nil {
return nil, err
}
return rollupFromGethConfig(cfg), nil
}
func initOpenEngineRPC(ctx *cli.Context, lgr log.Logger) (client.RPC, error) {
openEP := ctx.String(EngineOpenEndpoint.Name)
rpc, err := client.NewRPC(ctx.Context, lgr, openEP)
if err != nil {
return nil, fmt.Errorf("failed to dial open Engine endpoint %q: %w", openEP, err)
}
return rpc, nil
}
// rollupFromGethConfig returns a very incomplete rollup config with only the
// L2ChainID and (most) fork activation timestamps set.
//
// Because Delta was a pure CL fork, its time isn't set either.
//
// This incomplete [rollup.Config] can be used as a [sources.EngineVersionProvider].
func rollupFromGethConfig(cfg *params.ChainConfig) *rollup.Config {
return &rollup.Config{
L2ChainID: cfg.ChainID,
RegolithTime: cfg.RegolithTime,
CanyonTime: cfg.CanyonTime,
EcotoneTime: cfg.EcotoneTime,
InteropTime: cfg.InteropTime,
}
}
......@@ -374,41 +467,35 @@ var (
EngineBlockCmd = &cli.Command{
Name: "block",
Usage: "build the next block using the Engine API",
Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath,
Flags: withEngineFlags(
FeeRecipientFlag, RandaoFlag, BlockTimeFlag, BuildingTime, AllowGaps,
},
),
// TODO: maybe support transaction and tx pool engine flags, since we use op-geth?
// TODO: reorg flag
// TODO: finalize/safe flag
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, _ log.Logger) error {
settings := ParseBuildingArgs(ctx)
status, err := engine.Status(context.Background(), client)
status, err := engine.Status(context.Background(), client.RPC)
if err != nil {
return err
}
payload, err := engine.BuildBlock(context.Background(), client, status, settings)
payloadEnv, err := engine.BuildBlock(context.Background(), client, status, settings)
if err != nil {
return err
}
_, err = io.WriteString(ctx.App.Writer, payload.BlockHash.String())
return err
fmt.Fprintln(ctx.App.Writer, payloadEnv.ExecutionPayload.BlockHash)
return nil
}),
}
EngineAutoCmd = &cli.Command{
Name: "auto",
Usage: "Run a proof-of-nothing chain with fixed block time.",
Description: "The block time can be changed. The execution engine must be synced to a post-Merge state first.",
Flags: append(append([]cli.Flag{
EngineEndpoint, EngineJWTPath,
FeeRecipientFlag, RandaoFlag, BlockTimeFlag, BuildingTime, AllowGaps,
}, oplog.CLIFlags(envVarPrefix)...), opmetrics.CLIFlags(envVarPrefix)...),
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
logCfg := oplog.ReadCLIConfig(ctx)
l := oplog.NewLogger(oplog.AppOut(ctx), logCfg)
oplog.SetGlobalLogHandler(l.Handler())
Flags: append(withEngineFlags(
FeeRecipientFlag, RandaoFlag, BlockTimeFlag, BuildingTime, AllowGaps),
opmetrics.CLIFlags(envVarPrefix)...),
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, l log.Logger) error {
settings := ParseBuildingArgs(ctx)
// TODO: finalize/safe flag
......@@ -435,9 +522,9 @@ var (
}
EngineStatusCmd = &cli.Command{
Name: "status",
Flags: []cli.Flag{EngineEndpoint, EngineJWTPath},
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
stat, err := engine.Status(context.Background(), client)
Flags: withEngineFlags(),
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, _ log.Logger) error {
stat, err := engine.Status(context.Background(), client.RPC)
if err != nil {
return err
}
......@@ -448,16 +535,15 @@ var (
}
EngineCopyCmd = &cli.Command{
Name: "copy",
Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath,
Flags: withEngineFlags(
&cli.StringFlag{
Name: "source",
Usage: "Unauthenticated regular eth JSON RPC to pull block data from, can be HTTP/WS/IPC.",
Required: true,
EnvVars: prefixEnvVars("ENGINE"),
EnvVars: prefixEnvVars("SOURCE"),
},
},
Action: EngineAction(func(ctx *cli.Context, dest client.RPC) error {
),
Action: EngineAction(func(ctx *cli.Context, dest *sources.EngineAPIClient, _ log.Logger) error {
rpcClient, err := rpc.DialOptions(context.Background(), ctx.String("source"))
if err != nil {
return fmt.Errorf("failed to dial engine source endpoint: %w", err)
......@@ -470,13 +556,12 @@ var (
EngineCopyPayloadCmd = &cli.Command{
Name: "copy-payload",
Description: "Take the block by number from source and insert it to the engine with NewPayload. No other calls are made.",
Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath,
Flags: withEngineFlags(
&cli.StringFlag{
Name: "source",
Usage: "Unauthenticated regular eth JSON RPC to pull block data from, can be HTTP/WS/IPC.",
Required: true,
EnvVars: prefixEnvVars("ENGINE"),
EnvVars: prefixEnvVars("SOURCE"),
},
&cli.Uint64Flag{
Name: "number",
......@@ -484,8 +569,8 @@ var (
Required: true,
EnvVars: prefixEnvVars("NUMBER"),
},
},
Action: EngineAction(func(ctx *cli.Context, dest client.RPC) error {
),
Action: EngineAction(func(ctx *cli.Context, dest *sources.EngineAPIClient, _ log.Logger) error {
rpcClient, err := rpc.DialOptions(context.Background(), ctx.String("source"))
if err != nil {
return fmt.Errorf("failed to dial engine source endpoint: %w", err)
......@@ -498,8 +583,7 @@ var (
EngineSetForkchoiceCmd = &cli.Command{
Name: "set-forkchoice",
Description: "Set forkchoice, specify unsafe, safe and finalized blocks by number",
Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath,
Flags: withEngineFlags(
&cli.Uint64Flag{
Name: "unsafe",
Usage: "Block number of block to set as latest block",
......@@ -518,8 +602,8 @@ var (
Required: true,
EnvVars: prefixEnvVars("FINALIZED"),
},
},
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
),
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, _ log.Logger) error {
return engine.SetForkchoice(ctx.Context, client, ctx.Uint64("finalized"), ctx.Uint64("safe"), ctx.Uint64("unsafe"))
}),
}
......@@ -527,8 +611,7 @@ var (
EngineSetForkchoiceHashCmd = &cli.Command{
Name: "set-forkchoice-by-hash",
Description: "Set forkchoice, specify unsafe, safe and finalized blocks by hash",
Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath,
Flags: withEngineFlags(
&cli.StringFlag{
Name: "unsafe",
Usage: "Block hash of block to set as latest block",
......@@ -547,8 +630,8 @@ var (
Required: true,
EnvVars: prefixEnvVars("FINALIZED"),
},
},
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
),
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, _ log.Logger) error {
finalized := common.HexToHash(ctx.String("finalized"))
safe := common.HexToHash(ctx.String("safe"))
unsafe := common.HexToHash(ctx.String("unsafe"))
......@@ -556,20 +639,44 @@ var (
}),
}
EngineRewindCmd = &cli.Command{
Name: "rewind",
Description: "Rewind chain by number (destructive!)",
Flags: withEngineFlags(
&cli.Uint64Flag{
Name: "to",
Usage: "Block number to rewind chain to",
Required: true,
EnvVars: prefixEnvVars("REWIND_TO"),
},
&cli.BoolFlag{
Name: "set-head",
Usage: "Whether to also call debug_setHead when rewinding",
EnvVars: prefixEnvVars("REWIND_SET_HEAD"),
},
),
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, lgr log.Logger) error {
open, err := initOpenEngineRPC(ctx, lgr)
if err != nil {
return fmt.Errorf("failed to dial open RPC endpoint: %w", err)
}
return engine.Rewind(ctx.Context, lgr, client, open, ctx.Uint64("to"), ctx.Bool("set-head"))
}),
}
EngineJSONCmd = &cli.Command{
Name: "json",
Description: "read json values from remaining args, or STDIN, and use them as RPC params to call the engine RPC method (first arg)",
Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath,
Flags: withEngineFlags(
&cli.BoolFlag{
Name: "stdin",
Usage: "Read params from stdin instead",
Required: false,
EnvVars: prefixEnvVars("STDIN"),
},
},
),
ArgsUsage: "<rpc-method-name> [params...]",
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
Action: EngineAction(func(ctx *cli.Context, client *sources.EngineAPIClient, _ log.Logger) error {
if ctx.NArg() == 0 {
return fmt.Errorf("expected at least 1 argument: RPC method name")
}
......@@ -580,7 +687,7 @@ var (
} else {
args = ctx.Args().Tail()
}
return engine.RawJSONInteraction(ctx.Context, client, ctx.Args().Get(0), args, r, ctx.App.Writer)
return engine.RawJSONInteraction(ctx.Context, client.RPC, ctx.Args().Get(0), args, r, ctx.App.Writer)
}),
}
)
......@@ -603,7 +710,7 @@ var CheatCmd = &cli.Command{
var EngineCmd = &cli.Command{
Name: "engine",
Usage: "Engine API commands to build/reorg/finalize blocks.",
Usage: "Engine API commands to build/reorg/rewind/finalize/copy blocks.",
Description: "Each sub-command dials the engine API endpoint (with provided JWT secret) and then runs the action",
Subcommands: []*cli.Command{
EngineBlockCmd,
......@@ -613,6 +720,7 @@ var EngineCmd = &cli.Command{
EngineCopyPayloadCmd,
EngineSetForkchoiceCmd,
EngineSetForkchoiceHashCmd,
EngineRewindCmd,
EngineJSONCmd,
},
}
......@@ -10,48 +10,27 @@ import (
"strings"
"time"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources"
)
type PayloadAttributesV2 struct {
Timestamp uint64 `json:"timestamp"`
Random common.Hash `json:"prevRandao"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}
func (p PayloadAttributesV2) MarshalJSON() ([]byte, error) {
type PayloadAttributes struct {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}
var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp)
enc.Random = p.Random
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = make([]*types.Withdrawal, 0)
return json.Marshal(&enc)
}
func DialClient(ctx context.Context, endpoint string, jwtSecret [32]byte) (client.RPC, error) {
auth := node.NewJWTAuth(jwtSecret)
const (
methodEthGetBlockByNumber = "eth_getBlockByNumber"
methodDebugChainConfig = "debug_chainConfig"
methodDebugSetHead = "debug_setHead"
)
rpcClient, err := rpc.DialOptions(ctx, endpoint, rpc.WithHTTPAuth(auth))
if err != nil {
return nil, fmt.Errorf("failed to dial engine endpoint: %w", err)
}
return client.NewBaseRPCClient(rpcClient), nil
func GetChainConfig(ctx context.Context, open client.RPC) (cfg *params.ChainConfig, err error) {
err = open.CallContext(ctx, &cfg, methodDebugChainConfig)
return
}
type RPCBlock struct {
......@@ -77,49 +56,67 @@ func getHeader(ctx context.Context, client client.RPC, method string, tag string
return header, nil
}
func headSafeFinalized(ctx context.Context, client client.RPC) (head *types.Block, safe, finalized *types.Header, err error) {
head, err = getBlock(ctx, client, "eth_getBlockByNumber", "latest")
func headSafeFinalized(ctx context.Context, client client.RPC) (head, safe, finalized *types.Header, err error) {
head, err = getHeader(ctx, client, methodEthGetBlockByNumber, "latest")
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get block: %w", err)
return nil, nil, nil, fmt.Errorf("failed to get latest: %w", err)
}
safe, err = getHeader(ctx, client, "eth_getBlockByNumber", "safe")
safe, fin, err := safeFinalized(ctx, client)
return head, safe, fin, err
}
func headBlockSafeFinalized(ctx context.Context, client client.RPC) (head *types.Block, safe, finalized *types.Header, err error) {
head, err = getBlock(ctx, client, methodEthGetBlockByNumber, "latest")
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get latest: %w", err)
}
safe, fin, err := safeFinalized(ctx, client)
return head, safe, fin, err
}
func safeFinalized(ctx context.Context, client client.RPC) (safe, finalized *types.Header, err error) {
safe, err = getHeader(ctx, client, methodEthGetBlockByNumber, "safe")
if err != nil {
return head, nil, nil, fmt.Errorf("failed to get safe block: %w", err)
return nil, nil, fmt.Errorf("failed to get safe: %w", err)
}
finalized, err = getHeader(ctx, client, "eth_getBlockByNumber", "finalized")
finalized, err = getHeader(ctx, client, methodEthGetBlockByNumber, "finalized")
if err != nil {
return head, safe, nil, fmt.Errorf("failed to get finalized block: %w", err)
return safe, nil, fmt.Errorf("failed to get finalized: %w", err)
}
return head, safe, finalized, nil
return safe, finalized, nil
}
func insertBlock(ctx context.Context, client client.RPC, payload *engine.ExecutableData) error {
var payloadResult *engine.PayloadStatusV1
if err := client.CallContext(ctx, &payloadResult, "engine_newPayloadV2", payload); err != nil {
return fmt.Errorf("failed to insert block %d: %w", payload.Number, err)
func insertBlock(ctx context.Context, client *sources.EngineAPIClient, payloadEnv *eth.ExecutionPayloadEnvelope) error {
payload := payloadEnv.ExecutionPayload
payloadResult, err := client.NewPayload(ctx, payload, payloadEnv.ParentBeaconBlockRoot)
if err != nil {
return fmt.Errorf("failed to insert block %d: %w", payload.BlockNumber, err)
}
if payloadResult.Status != string(eth.ExecutionValid) {
if payloadResult.Status != eth.ExecutionValid {
return fmt.Errorf("block insertion was not valid: %v", payloadResult.ValidationError)
}
return nil
}
func updateForkchoice(ctx context.Context, client client.RPC, head, safe, finalized common.Hash) error {
var post engine.ForkChoiceResponse
if err := client.CallContext(ctx, &post, "engine_forkchoiceUpdatedV2",
engine.ForkchoiceStateV1{
HeadBlockHash: head,
SafeBlockHash: safe,
FinalizedBlockHash: finalized,
}, nil); err != nil {
return fmt.Errorf("failed to set forkchoice with new block %s: %w", head, err)
func updateForkchoice(ctx context.Context, client *sources.EngineAPIClient, head, safe, finalized common.Hash) error {
res, err := client.ForkchoiceUpdate(ctx, &eth.ForkchoiceState{
HeadBlockHash: head,
SafeBlockHash: safe,
FinalizedBlockHash: finalized,
}, nil)
if err != nil {
return fmt.Errorf("failed to update forkchoice with new head %s: %w", head, err)
}
if post.PayloadStatus.Status != string(eth.ExecutionValid) {
return fmt.Errorf("post-block forkchoice update was not valid: %v", post.PayloadStatus.ValidationError)
if res.PayloadStatus.Status != eth.ExecutionValid {
return fmt.Errorf("forkchoice update was not valid: %v", res.PayloadStatus.ValidationError)
}
return nil
}
func debugSetHead(ctx context.Context, open client.RPC, head uint64) error {
return open.CallContext(ctx, nil, methodDebugSetHead, hexutil.Uint64(head))
}
type BlockBuildingSettings struct {
BlockTime uint64
// skip a block; timestamps will still increase in multiples of BlockTime like L1, but there may be gaps.
......@@ -129,7 +126,7 @@ type BlockBuildingSettings struct {
BuildTime time.Duration
}
func BuildBlock(ctx context.Context, client client.RPC, status *StatusData, settings *BlockBuildingSettings) (*engine.ExecutableData, error) {
func BuildBlock(ctx context.Context, client *sources.EngineAPIClient, status *StatusData, settings *BlockBuildingSettings) (*eth.ExecutionPayloadEnvelope, error) {
timestamp := status.Head.Time + settings.BlockTime
if settings.AllowGaps {
now := uint64(time.Now().Unix())
......@@ -137,20 +134,17 @@ func BuildBlock(ctx context.Context, client client.RPC, status *StatusData, sett
timestamp = now - ((now - timestamp) % settings.BlockTime)
}
}
var pre engine.ForkChoiceResponse
if err := client.CallContext(ctx, &pre, "engine_forkchoiceUpdatedV2",
engine.ForkchoiceStateV1{
attrs := newPayloadAttributes(client.EngineVersionProvider(), timestamp, settings.Random, settings.FeeRecipient)
pre, err := client.ForkchoiceUpdate(ctx,
&eth.ForkchoiceState{
HeadBlockHash: status.Head.Hash,
SafeBlockHash: status.Safe.Hash,
FinalizedBlockHash: status.Finalized.Hash,
}, PayloadAttributesV2{
Timestamp: timestamp,
Random: settings.Random,
SuggestedFeeRecipient: settings.FeeRecipient,
}); err != nil {
}, attrs)
if err != nil {
return nil, fmt.Errorf("failed to set forkchoice when building new block: %w", err)
}
if pre.PayloadStatus.Status != string(eth.ExecutionValid) {
if pre.PayloadStatus.Status != eth.ExecutionValid {
return nil, fmt.Errorf("pre-block forkchoice update was not valid: %v", pre.PayloadStatus.ValidationError)
}
......@@ -161,26 +155,45 @@ func BuildBlock(ctx context.Context, client client.RPC, status *StatusData, sett
case <-time.After(settings.BuildTime):
}
var payload *engine.ExecutionPayloadEnvelope
if err := client.CallContext(ctx, &payload, "engine_getPayloadV2", pre.PayloadID); err != nil {
payload, err := client.GetPayload(ctx, eth.PayloadInfo{ID: *pre.PayloadID, Timestamp: timestamp})
if err != nil {
return nil, fmt.Errorf("failed to get payload %v, %d time after instructing engine to build it: %w", pre.PayloadID, settings.BuildTime, err)
}
if err := insertBlock(ctx, client, payload.ExecutionPayload); err != nil {
if err := insertBlock(ctx, client, payload); err != nil {
return nil, err
}
if err := updateForkchoice(ctx, client, payload.ExecutionPayload.BlockHash, status.Safe.Hash, status.Finalized.Hash); err != nil {
return nil, err
}
return payload.ExecutionPayload, nil
return payload, nil
}
func newPayloadAttributes(evp sources.EngineVersionProvider, timestamp uint64, prevRandao common.Hash, feeRecipient common.Address) *eth.PayloadAttributes {
pa := &eth.PayloadAttributes{
Timestamp: hexutil.Uint64(timestamp),
PrevRandao: eth.Bytes32(prevRandao),
SuggestedFeeRecipient: feeRecipient,
}
ver := evp.ForkchoiceUpdatedVersion(pa)
if ver == eth.FCUV2 || ver == eth.FCUV3 {
withdrawals := make(types.Withdrawals, 0)
pa.Withdrawals = &withdrawals
}
if ver == eth.FCUV3 {
pa.ParentBeaconBlockRoot = new(common.Hash)
}
return pa
}
func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logger, shutdown <-chan struct{}, settings *BlockBuildingSettings) error {
func Auto(ctx context.Context, metrics Metricer, client *sources.EngineAPIClient, log log.Logger, shutdown <-chan struct{}, settings *BlockBuildingSettings) error {
ticker := time.NewTicker(time.Millisecond * 100)
defer ticker.Stop()
var lastPayload *engine.ExecutableData
var lastPayload *eth.ExecutionPayload
var buildErr error
for {
select {
......@@ -194,7 +207,7 @@ func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logg
blockTime := time.Duration(settings.BlockTime) * time.Second
lastTime := uint64(0)
if lastPayload != nil {
lastTime = lastPayload.Timestamp
lastTime = uint64(lastPayload.Timestamp)
}
buildTriggerTime := time.Unix(int64(lastTime), 0).Add(blockTime - settings.BuildTime)
......@@ -206,7 +219,7 @@ func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logg
buildTime = 10 * time.Millisecond
}
buildErr = nil
status, err := Status(ctx, client)
status, err := Status(ctx, client.RPC)
if err != nil {
log.Error("failed to get pre-block engine status", "err", err)
metrics.RecordBlockFail()
......@@ -220,7 +233,7 @@ func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logg
// There are no gap slots, so we just go back 32 blocks.
if status.Head.Number%32 == 0 {
if status.Safe.Number+32 <= status.Head.Number {
safe, err := getHeader(ctx, client, "eth_getBlockByNumber", hexutil.Uint64(status.Head.Number-32).String())
safe, err := getHeader(ctx, client.RPC, methodEthGetBlockByNumber, hexutil.Uint64(status.Head.Number-32).String())
if err != nil {
buildErr = err
log.Error("failed to find block for new safe block progress", "err", err)
......@@ -229,7 +242,7 @@ func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logg
status.Safe = eth.L1BlockRef{Hash: safe.Hash(), Number: safe.Number.Uint64(), Time: safe.Time, ParentHash: safe.ParentHash}
}
if status.Finalized.Number+32 <= status.Safe.Number {
finalized, err := getHeader(ctx, client, "eth_getBlockByNumber", hexutil.Uint64(status.Safe.Number-32).String())
finalized, err := getHeader(ctx, client.RPC, methodEthGetBlockByNumber, hexutil.Uint64(status.Safe.Number-32).String())
if err != nil {
buildErr = err
log.Error("failed to find block for new finalized block progress", "err", err)
......@@ -239,7 +252,7 @@ func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logg
}
}
payload, err := BuildBlock(ctx, client, status, &BlockBuildingSettings{
payloadEnv, err := BuildBlock(ctx, client, status, &BlockBuildingSettings{
BlockTime: settings.BlockTime,
AllowGaps: settings.AllowGaps,
Random: settings.Random,
......@@ -251,12 +264,16 @@ func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logg
log.Error("failed to produce block", "err", err)
metrics.RecordBlockFail()
} else {
payload := payloadEnv.ExecutionPayload
lastPayload = payload
log.Info("created block", "hash", payload.BlockHash, "number", payload.Number,
log.Info("created block", "hash", payload.BlockHash, "number", payload.BlockNumber,
"timestamp", payload.Timestamp, "txs", len(payload.Transactions),
"gas", payload.GasUsed, "basefee", payload.BaseFeePerGas)
basefee, _ := new(big.Float).SetInt(payload.BaseFeePerGas).Float64()
metrics.RecordBlockStats(payload.BlockHash, payload.Number, payload.Timestamp, uint64(len(payload.Transactions)), payload.GasUsed, basefee)
basefee := (*uint256.Int)(&payload.BaseFeePerGas).Float64()
metrics.RecordBlockStats(
payload.BlockHash, uint64(payload.BlockNumber), uint64(payload.Timestamp),
uint64(len(payload.Transactions)),
uint64(payload.GasUsed), basefee)
}
}
}
......@@ -274,7 +291,7 @@ type StatusData struct {
}
func Status(ctx context.Context, client client.RPC) (*StatusData, error) {
head, safe, finalized, err := headSafeFinalized(ctx, client)
head, safe, finalized, err := headBlockSafeFinalized(ctx, client)
if err != nil {
return nil, err
}
......@@ -291,58 +308,73 @@ func Status(ctx context.Context, client client.RPC) (*StatusData, error) {
// Copy takes the forkchoice state of copyFrom, and applies it to copyTo, and inserts the head-block.
// The destination engine should then start syncing to this new chain if it has peers to do so.
func Copy(ctx context.Context, copyFrom client.RPC, copyTo client.RPC) error {
copyHead, copySafe, copyFinalized, err := headSafeFinalized(ctx, copyFrom)
func Copy(ctx context.Context, copyFrom client.RPC, copyTo *sources.EngineAPIClient) error {
copyHead, copySafe, copyFinalized, err := headBlockSafeFinalized(ctx, copyFrom)
if err != nil {
return err
}
payloadEnv, err := blockAsPayloadEnv(copyHead, copyTo.EngineVersionProvider())
if err != nil {
return err
}
payloadEnv := engine.BlockToExecutableData(copyHead, nil, nil)
if err := updateForkchoice(ctx, copyTo, copyHead.ParentHash(), copySafe.Hash(), copyFinalized.Hash()); err != nil {
return err
}
payload := payloadEnv.ExecutionPayload
if err := insertBlock(ctx, copyTo, payload); err != nil {
if err := insertBlock(ctx, copyTo, payloadEnv); err != nil {
return err
}
if err := updateForkchoice(ctx, copyTo, payload.BlockHash, copySafe.Hash(), copyFinalized.Hash()); err != nil {
if err := updateForkchoice(ctx, copyTo,
payloadEnv.ExecutionPayload.BlockHash, copySafe.Hash(), copyFinalized.Hash()); err != nil {
return err
}
return nil
}
// CopyPaylod takes the execution payload at number & applies it via NewPayload to copyTo
func CopyPayload(ctx context.Context, number uint64, copyFrom client.RPC, copyTo client.RPC) error {
copyHead, err := getBlock(ctx, copyFrom, "eth_getBlockByNumber", hexutil.EncodeUint64(number))
func CopyPayload(ctx context.Context, number uint64, copyFrom client.RPC, copyTo *sources.EngineAPIClient) error {
copyHead, err := getBlock(ctx, copyFrom, methodEthGetBlockByNumber, hexutil.EncodeUint64(number))
if err != nil {
return err
}
payloadEnv := engine.BlockToExecutableData(copyHead, nil, nil)
payload := payloadEnv.ExecutionPayload
if err := insertBlock(ctx, copyTo, payload); err != nil {
payloadEnv, err := blockAsPayloadEnv(copyHead, copyTo.EngineVersionProvider())
if err != nil {
return err
}
if err := insertBlock(ctx, copyTo, payloadEnv); err != nil {
return err
}
return nil
}
func SetForkchoice(ctx context.Context, client client.RPC, finalizedNum, safeNum, unsafeNum uint64) error {
func blockAsPayloadEnv(block *types.Block, evp sources.EngineVersionProvider) (*eth.ExecutionPayloadEnvelope, error) {
var canyon *uint64
// hack: if we're calling at least FCUV2, get empty withdrawals by setting Canyon before the block time
if v := evp.ForkchoiceUpdatedVersion(&eth.PayloadAttributes{Timestamp: hexutil.Uint64(block.Time())}); v != eth.FCUV1 {
canyon = new(uint64)
}
return eth.BlockAsPayloadEnv(block, canyon)
}
func SetForkchoice(ctx context.Context, client *sources.EngineAPIClient, finalizedNum, safeNum, unsafeNum uint64) error {
if unsafeNum < safeNum {
return fmt.Errorf("cannot set unsafe (%d) < safe (%d)", unsafeNum, safeNum)
}
if safeNum < finalizedNum {
return fmt.Errorf("cannot set safe (%d) < finalized (%d)", safeNum, finalizedNum)
}
head, err := getHeader(ctx, client, "eth_getBlockByNumber", "latest")
head, err := getHeader(ctx, client.RPC, methodEthGetBlockByNumber, "latest")
if err != nil {
return fmt.Errorf("failed to get latest block: %w", err)
}
if unsafeNum > head.Number.Uint64() {
return fmt.Errorf("cannot set unsafe (%d) > latest (%d)", unsafeNum, head.Number.Uint64())
}
finalizedHeader, err := getHeader(ctx, client, "eth_getBlockByNumber", hexutil.Uint64(finalizedNum).String())
finalizedHeader, err := getHeader(ctx, client.RPC, methodEthGetBlockByNumber, hexutil.Uint64(finalizedNum).String())
if err != nil {
return fmt.Errorf("failed to get block %d to mark finalized: %w", finalizedNum, err)
}
safeHeader, err := getHeader(ctx, client, "eth_getBlockByNumber", hexutil.Uint64(safeNum).String())
safeHeader, err := getHeader(ctx, client.RPC, methodEthGetBlockByNumber, hexutil.Uint64(safeNum).String())
if err != nil {
return fmt.Errorf("failed to get block %d to mark safe: %w", safeNum, err)
}
......@@ -352,13 +384,50 @@ func SetForkchoice(ctx context.Context, client client.RPC, finalizedNum, safeNum
return nil
}
func SetForkchoiceByHash(ctx context.Context, client client.RPC, finalized, safe, unsafe common.Hash) error {
func SetForkchoiceByHash(ctx context.Context, client *sources.EngineAPIClient, finalized, safe, unsafe common.Hash) error {
if err := updateForkchoice(ctx, client, unsafe, safe, finalized); err != nil {
return fmt.Errorf("failed to update forkchoice: %w", err)
}
return nil
}
func Rewind(ctx context.Context, lgr log.Logger, client *sources.EngineAPIClient, open client.RPC, to uint64, setHead bool) error {
unsafe, err := getHeader(ctx, open, methodEthGetBlockByNumber, hexutil.Uint64(to).String())
if err != nil {
return fmt.Errorf("failed to get header %d: %w", to, err)
}
toUnsafe := eth.HeaderBlockID(unsafe)
latest, safe, finalized, err := headSafeFinalized(ctx, open)
if err != nil {
return fmt.Errorf("failed to get current heads: %w", err)
}
// when rewinding, don't increase unsafe/finalized tags
toSafe, toFinalized := toUnsafe, toUnsafe
if safe.Number.Uint64() < to {
toSafe = eth.HeaderBlockID(safe)
}
if finalized.Number.Uint64() < to {
toFinalized = eth.HeaderBlockID(finalized)
}
lgr.Info("Rewinding chain",
"setHead", setHead,
"latest", eth.HeaderBlockID(latest),
"unsafe", toUnsafe,
"safe", toSafe,
"finalized", toFinalized,
)
if setHead {
lgr.Debug("Calling "+methodDebugSetHead, "head", to)
if err := debugSetHead(ctx, open, to); err != nil {
return fmt.Errorf("failed to setHead %d: %w", to, err)
}
}
return SetForkchoiceByHash(ctx, client, toFinalized.Hash, toSafe.Hash, toUnsafe.Hash)
}
func RawJSONInteraction(ctx context.Context, client client.RPC, method string, args []string, input io.Reader, output io.Writer) error {
var params []any
if input != nil {
......
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