Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

Commit

Permalink
ref impl: execution engine API client-bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
protolambda committed Jan 10, 2022
1 parent 488ed1a commit b6ca65c
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 0 deletions.
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ go 1.17

require (
github.com/ethereum/go-ethereum v1.10.13
github.com/holiman/uint256 v1.2.0
github.com/protolambda/ask v0.1.2
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
)

require (
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
)

replace github.com/ethereum/go-ethereum v1.10.13 => github.com/ethereum-optimism/reference-optimistic-geth v0.0.0-20220107224313-7f6d88bc156a
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
Expand Down Expand Up @@ -177,13 +178,15 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM=
Expand Down Expand Up @@ -286,6 +289,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
Expand Down Expand Up @@ -327,6 +331,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
Expand Down Expand Up @@ -558,6 +563,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
Expand All @@ -569,6 +575,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
297 changes: 297 additions & 0 deletions opnode/l2/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package l2

import (
"context"
"fmt"
"reflect"

"github.com/ethereum-optimism/optimistic-specs/opnode/eth"

"github.com/ethereum/go-ethereum/log"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/holiman/uint256"

"github.com/ethereum/go-ethereum/rpc"
)

type ErrorCode int

const (
UnavailablePayload ErrorCode = -32001
)

type Bytes32 [32]byte

func (b *Bytes32) UnmarshalJSON(text []byte) error {
return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:])
}

func (b *Bytes32) UnmarshalText(text []byte) error {
return hexutil.UnmarshalFixedText("Bytes32", text, b[:])
}

func (b Bytes32) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}

func (b Bytes32) String() string {
return hexutil.Encode(b[:])
}

type Bytes256 [256]byte

func (b *Bytes256) UnmarshalJSON(text []byte) error {
return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:])
}

func (b *Bytes256) UnmarshalText(text []byte) error {
return hexutil.UnmarshalFixedText("Bytes32", text, b[:])
}

func (b Bytes256) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}

func (b Bytes256) String() string {
return hexutil.Encode(b[:])
}

type Uint64Quantity = hexutil.Uint64

type BytesMax32 []byte

func (b *BytesMax32) UnmarshalJSON(text []byte) error {
if len(text) > 64+2+2 { // account for delimiter "", and 0x prefix
return fmt.Errorf("input too long, expected at most 32 hex-encoded, 0x-prefixed, bytes: %x", text)
}
return (*hexutil.Bytes)(b).UnmarshalJSON(text)
}

func (b *BytesMax32) UnmarshalText(text []byte) error {
if len(text) > 64+2 { // account for 0x prefix
return fmt.Errorf("input too long, expected at most 32 hex-encoded, 0x-prefixed, bytes: %x", text)
}
return (*hexutil.Bytes)(b).UnmarshalText(text)
}

func (b BytesMax32) MarshalText() ([]byte, error) {
return (hexutil.Bytes)(b).MarshalText()
}

func (b BytesMax32) String() string {
return hexutil.Encode(b)
}

type Uint256Quantity = uint256.Int

type Data = hexutil.Bytes

// TODO: implement neat 8 byte typed payload ID and upstream it to geth api definitions
type PayloadID = hexutil.Bytes

type ExecutionPayload struct {
ParentHash common.Hash `json:"parentHash"`
FeeRecipient common.Address `json:"feeRecipient"`
StateRoot Bytes32 `json:"stateRoot"`
ReceiptsRoot Bytes32 `json:"receiptsRoot"`
LogsBloom Bytes256 `json:"logsBloom"`
Random Bytes32 `json:"random"`
BlockNumber Uint64Quantity `json:"blockNumber"`
GasLimit Uint64Quantity `json:"gasLimit"`
GasUsed Uint64Quantity `json:"gasUsed"`
Timestamp Uint64Quantity `json:"timestamp"`
ExtraData BytesMax32 `json:"extraData"`
BaseFeePerGas Uint256Quantity `json:"baseFeePerGas"`
BlockHash common.Hash `json:"blockHash"`
// Array of transaction objects, each object is a byte list (DATA) representing
// TransactionType || TransactionPayload or LegacyTransaction as defined in EIP-2718
Transactions []Data `json:"transactions"`
}

func (payload *ExecutionPayload) ID() eth.BlockID {
return eth.BlockID{Hash: payload.BlockHash, Number: uint64(payload.BlockNumber)}
}

type PayloadAttributes struct {
// value for the timestamp field of the new payload
Timestamp Uint64Quantity `json:"timestamp"`
// value for the random field of the new payload
Random Bytes32 `json:"random"`
// suggested value for the coinbase field of the new payload
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient"`
// Transactions to build the block with, omitted if the local tx pool of the engine should be used instead
Transactions []Data `json:"transactions,omitempty"`
}

type ExecutePayloadStatus string

const (
// given payload is valid
ExecutionValid ExecutePayloadStatus = "VALID"
// given payload is invalid
ExecutionInvalid ExecutePayloadStatus = "INVALID"
// sync process is in progress
ExecutionSyncing ExecutePayloadStatus = "SYNCING"
)

type ExecutePayloadResult struct {
// the result of the payload execution
Status ExecutePayloadStatus `json:"status"`
// the hash of the most recent valid block in the branch defined by payload and its ancestors
LatestValidHash common.Hash `json:"latestValidHash"`
// additional details on the result
ValidationError string `json:"validationError"`
}

type ForkchoiceState struct {
// block hash of the head of the canonical chain
HeadBlockHash common.Hash `json:"headBlockHash"`
// safe block hash in the canonical chain
SafeBlockHash common.Hash `json:"safeBlockHash"`
// block hash of the most recent finalized block
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
}

type ForkchoiceUpdatedStatus string

const (
// given payload is valid
UpdateSuccess ForkchoiceUpdatedStatus = "SUCCESS"
// sync process is in progress
UpdateSyncing ForkchoiceUpdatedStatus = "SYNCING"
)

type ForkchoiceUpdatedResult struct {
// the result of the payload execution
Status ForkchoiceUpdatedStatus `json:"status"`
// the payload id if requested
PayloadID *PayloadID `json:"payloadId"`
}

type EngineAPI interface {
GetPayload(ctx context.Context, payloadId PayloadID) (*ExecutionPayload, error)
ExecutePayload(ctx context.Context, payload *ExecutionPayload) (*ExecutePayloadResult, error)
ForkchoiceUpdated(ctx context.Context, state *ForkchoiceState, attr *PayloadAttributes) (ForkchoiceUpdatedResult, error)
Close()
}

type RPCBackend interface {
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
Close()
}

type EthBackend interface {
eth.BlockByHashSource
eth.BlockByNumberSource
eth.NewHeadSource
}

type EngineClient struct {
RPCBackend
EthBackend
Log log.Logger
}

func (el *EngineClient) GetPayload(ctx context.Context, payloadId PayloadID) (*ExecutionPayload, error) {
e := el.Log.New("payload_id", payloadId)
e.Debug("getting payload")
var result ExecutionPayload
err := el.CallContext(ctx, &result, "engine_getPayloadV1", payloadId)
if err != nil {
e = log.New("payload_id", "err", err)
if rpcErr, ok := err.(rpc.Error); ok {
code := ErrorCode(rpcErr.ErrorCode())
if code != UnavailablePayload {
e.Warn("unexpected error code in get-payload response", "code", code)
} else {
e.Warn("unavailable payload in get-payload request")
}
} else {
e.Error("failed to get payload")
}
return nil, err
}
e.Debug("Received payload")
return &result, nil
}

func (el *EngineClient) ExecutePayload(ctx context.Context, payload *ExecutionPayload) (*ExecutePayloadResult, error) {
e := el.Log.New("block_hash", payload.BlockHash)
e.Debug("sending payload for execution")
var result ExecutePayloadResult
err := el.CallContext(ctx, &result, "engine_executePayloadV1", payload)
if err != nil {
e.Error("Payload execution failed", "err", err)
return nil, err
}
e.Debug("Received payload execution result", "status", result.Status, "latestValidHash", result.LatestValidHash, "message", result.ValidationError)
return &result, nil
}

func (el *EngineClient) ForkchoiceUpdated(ctx context.Context, state *ForkchoiceState, attr *PayloadAttributes) (ForkchoiceUpdatedResult, error) {
e := el.Log.New("state", state, "attr", attr)
e.Debug("Sharing forkchoice-updated signal")

var result ForkchoiceUpdatedResult
err := el.CallContext(ctx, &result, "engine_forkchoiceUpdatedV1", state, attr)
if err == nil {
e.Debug("Shared forkchoice-updated signal")
if attr != nil {
e.Debug("Received payload id", "payloadId", result.PayloadID)
}
return result, nil
} else {
e = e.New("err", err)
if rpcErr, ok := err.(rpc.Error); ok {
code := ErrorCode(rpcErr.ErrorCode())
e.Warn("Unexpected error code in forkchoice-updated response", "code", code)
} else {
e.Error("Failed to share forkchoice-updated signal")
}
return result, err
}
}

func (el *EngineClient) Close() {
el.RPCBackend.Close()
}

func BlockToPayload(bl *types.Block, random Bytes32) (*ExecutionPayload, error) {
extra := bl.Extra()
if len(extra) > 32 {
return nil, fmt.Errorf("eth2 merge spec limits extra data to 32 bytes in payload, got %d", len(extra))
}
baseFee, overflow := uint256.FromBig(bl.BaseFee())
if overflow {
return nil, fmt.Errorf("overflowing base fee")
}
txs := bl.Transactions()
txsEncoded := make([]Data, 0, len(txs))
for i, tx := range txs {
txOpaque, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to encode tx %d", i)
}
txsEncoded = append(txsEncoded, txOpaque)
}
return &ExecutionPayload{
ParentHash: bl.ParentHash(),
FeeRecipient: bl.Coinbase(),
StateRoot: Bytes32(bl.Root()),
ReceiptsRoot: Bytes32(bl.ReceiptHash()),
LogsBloom: Bytes256(bl.Bloom()),
Random: random,
BlockNumber: Uint64Quantity(bl.NumberU64()),
GasLimit: Uint64Quantity(bl.GasLimit()),
GasUsed: Uint64Quantity(bl.GasUsed()),
Timestamp: Uint64Quantity(bl.Time()),
ExtraData: BytesMax32(extra),
BaseFeePerGas: Uint256Quantity(*baseFee),
BlockHash: bl.Hash(),
Transactions: txsEncoded,
}, nil
}

0 comments on commit b6ca65c

Please sign in to comment.