Skip to content

Commit bf4eb09

Browse files
committed
simulators/eth2/engine: Add test cases
1 parent bb633bc commit bf4eb09

File tree

12 files changed

+1087
-148
lines changed

12 files changed

+1087
-148
lines changed

simulators/eth2/engine/engineapi.go

Lines changed: 257 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,189 @@
11
package main
22

33
import (
4+
"context"
5+
"encoding/hex"
46
"encoding/json"
57
"fmt"
68
"math/big"
9+
"net"
10+
"net/http"
11+
"time"
712

813
"github.com/ethereum/go-ethereum/common"
914
"github.com/ethereum/go-ethereum/common/hexutil"
15+
"github.com/ethereum/go-ethereum/core/types"
16+
"github.com/ethereum/go-ethereum/ethclient"
17+
"github.com/ethereum/go-ethereum/rpc"
18+
"github.com/ethereum/hive/hivesim"
19+
"github.com/golang-jwt/jwt/v4"
1020
)
1121

22+
// https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.7/src/engine/specification.md
23+
var EthPortHTTP = 8545
24+
var EnginePortHTTP = 8551
25+
26+
// EngineClient wrapper for Ethereum Engine RPC for testing purposes.
27+
type EngineClient struct {
28+
*hivesim.T
29+
*hivesim.Client
30+
c *rpc.Client
31+
Eth *ethclient.Client
32+
cEth *rpc.Client
33+
IP net.IP
34+
TerminalTotalDifficulty *big.Int
35+
36+
// This holds most recent context created by the Ctx method.
37+
// Every time Ctx is called, it creates a new context with the default
38+
// timeout and cancels the previous one.
39+
lastCtx context.Context
40+
lastCancel context.CancelFunc
41+
}
42+
43+
// NewClient creates a engine client that uses the given RPC client.
44+
func NewEngineClient(t *hivesim.T, userRPCAddress string, engineRPCAddress string, ttd *big.Int) *EngineClient {
45+
client := &http.Client{}
46+
// Prepare HTTP Client
47+
rpcHttpClient, _ := rpc.DialHTTPWithClient(engineRPCAddress, client)
48+
49+
// Prepare ETH Client
50+
client = &http.Client{}
51+
52+
rpcClient, _ := rpc.DialHTTPWithClient(userRPCAddress, client)
53+
eth := ethclient.NewClient(rpcClient)
54+
return &EngineClient{
55+
T: t,
56+
c: rpcHttpClient,
57+
Eth: eth,
58+
cEth: rpcClient,
59+
TerminalTotalDifficulty: ttd,
60+
lastCtx: nil,
61+
lastCancel: nil,
62+
}
63+
}
64+
65+
func (ec *EngineClient) Close() {
66+
ec.c.Close()
67+
ec.Eth.Close()
68+
if ec.lastCtx != nil {
69+
ec.lastCancel()
70+
}
71+
}
72+
73+
func (ec *EngineClient) Ctx() context.Context {
74+
if ec.lastCtx != nil {
75+
ec.lastCancel()
76+
}
77+
ec.lastCtx, ec.lastCancel = context.WithTimeout(context.Background(), 10*time.Second)
78+
return ec.lastCtx
79+
}
80+
81+
// Helper structs to fetch the TotalDifficulty
82+
type TD struct {
83+
TotalDifficulty *hexutil.Big `json:"totalDifficulty"`
84+
}
85+
type TotalDifficultyHeader struct {
86+
types.Header
87+
TD
88+
}
89+
90+
func (tdh *TotalDifficultyHeader) UnmarshalJSON(data []byte) error {
91+
if err := json.Unmarshal(data, &tdh.Header); err != nil {
92+
return err
93+
}
94+
if err := json.Unmarshal(data, &tdh.TD); err != nil {
95+
return err
96+
}
97+
return nil
98+
}
99+
100+
func (ec *EngineClient) checkTTD() (*types.Header, bool) {
101+
var td *TotalDifficultyHeader
102+
if err := ec.cEth.CallContext(ec.Ctx(), &td, "eth_getBlockByNumber", "latest", false); err != nil {
103+
panic(err)
104+
}
105+
if td.TotalDifficulty.ToInt().Cmp(ec.TerminalTotalDifficulty) >= 0 {
106+
return &td.Header, true
107+
}
108+
return nil, false
109+
}
110+
111+
// Engine API Types
112+
type PayloadStatusV1 struct {
113+
Status PayloadStatus `json:"status"`
114+
LatestValidHash *common.Hash `json:"latestValidHash"`
115+
ValidationError *string `json:"validationError"`
116+
}
117+
type ForkChoiceResponse struct {
118+
PayloadStatus PayloadStatusV1 `json:"payloadStatus"`
119+
PayloadID *PayloadID `json:"payloadId"`
120+
}
121+
122+
type PayloadStatus int
123+
124+
const (
125+
Unknown PayloadStatus = iota
126+
Valid
127+
Invalid
128+
Accepted
129+
Syncing
130+
InvalidBlockHash
131+
)
132+
133+
var PayloadStatuses = map[PayloadStatus]string{
134+
Valid: "VALID",
135+
Invalid: "INVALID",
136+
Accepted: "ACCEPTED",
137+
Syncing: "SYNCING",
138+
InvalidBlockHash: "INVALID_BLOCK_HASH",
139+
}
140+
141+
func (b PayloadStatus) String() string {
142+
str, ok := PayloadStatuses[b]
143+
if !ok {
144+
return "UNKNOWN"
145+
}
146+
return str
147+
}
148+
149+
func (b PayloadStatus) MarshalText() ([]byte, error) {
150+
str, ok := PayloadStatuses[b]
151+
if !ok {
152+
return nil, fmt.Errorf("invalid payload status")
153+
}
154+
return []byte(str), nil
155+
}
156+
157+
func (b *PayloadStatus) UnmarshalText(input []byte) error {
158+
s := string(input)
159+
for p, status := range PayloadStatuses {
160+
if status == s {
161+
*b = p
162+
return nil
163+
}
164+
}
165+
return fmt.Errorf("invalid payload status: %s", s)
166+
}
167+
168+
type PayloadID [8]byte
169+
170+
func (b PayloadID) MarshalText() ([]byte, error) {
171+
return hexutil.Bytes(b[:]).MarshalText()
172+
}
173+
func (b *PayloadID) UnmarshalText(input []byte) error {
174+
err := hexutil.UnmarshalFixedText("PayloadID", input, b[:])
175+
if err != nil {
176+
return fmt.Errorf("invalid payload id %q: %w", input, err)
177+
}
178+
return nil
179+
}
180+
181+
type ForkchoiceStateV1 struct {
182+
HeadBlockHash common.Hash `json:"headBlockHash"`
183+
SafeBlockHash common.Hash `json:"safeBlockHash"`
184+
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
185+
}
186+
12187
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
13188
// ExecutableDataV1 structure described at https://github.com/ethereum/execution-apis/src/engine/specification.md
14189
type ExecutableDataV1 struct {
@@ -40,6 +215,74 @@ type executableDataMarshaling struct {
40215
Transactions []hexutil.Bytes
41216
}
42217

218+
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
219+
// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74
220+
type PayloadAttributesV1 struct {
221+
Timestamp uint64 `json:"timestamp" gencodec:"required"`
222+
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
223+
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
224+
}
225+
226+
// JSON type overrides for PayloadAttributesV1.
227+
type payloadAttributesMarshaling struct {
228+
Timestamp hexutil.Uint64
229+
}
230+
231+
// JWT Tokens
232+
func GetNewToken(jwtSecretBytes []byte, iat time.Time) (string, error) {
233+
newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
234+
"iat": iat.Unix(),
235+
})
236+
tokenString, err := newToken.SignedString(jwtSecretBytes)
237+
if err != nil {
238+
return "", err
239+
}
240+
return tokenString, nil
241+
}
242+
243+
func (ec *EngineClient) PrepareAuthCallToken(jwtSecretBytes []byte, iat time.Time) error {
244+
newTokenString, err := GetNewToken(jwtSecretBytes, iat)
245+
if err != nil {
246+
return err
247+
}
248+
ec.c.SetHeader("Authorization", fmt.Sprintf("Bearer %s", newTokenString))
249+
return nil
250+
}
251+
252+
func (ec *EngineClient) PrepareDefaultAuthCallToken() error {
253+
secret, _ := hex.DecodeString("7365637265747365637265747365637265747365637265747365637265747365")
254+
ec.PrepareAuthCallToken(secret, time.Now())
255+
return nil
256+
}
257+
258+
// Engine API Call Methods
259+
func (ec *EngineClient) EngineForkchoiceUpdatedV1(fcState *ForkchoiceStateV1, pAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
260+
var result ForkChoiceResponse
261+
if err := ec.PrepareDefaultAuthCallToken(); err != nil {
262+
return result, err
263+
}
264+
err := ec.c.CallContext(ec.Ctx(), &result, "engine_forkchoiceUpdatedV1", fcState, pAttributes)
265+
return result, err
266+
}
267+
268+
func (ec *EngineClient) EngineGetPayloadV1(payloadId *PayloadID) (ExecutableDataV1, error) {
269+
var result ExecutableDataV1
270+
if err := ec.PrepareDefaultAuthCallToken(); err != nil {
271+
return result, err
272+
}
273+
err := ec.c.CallContext(ec.Ctx(), &result, "engine_getPayloadV1", payloadId)
274+
return result, err
275+
}
276+
277+
func (ec *EngineClient) EngineNewPayloadV1(payload *ExecutableDataV1) (PayloadStatusV1, error) {
278+
var result PayloadStatusV1
279+
if err := ec.PrepareDefaultAuthCallToken(); err != nil {
280+
return result, err
281+
}
282+
err := ec.c.CallContext(ec.Ctx(), &result, "engine_newPayloadV1", payload)
283+
return result, err
284+
}
285+
43286
// Json helpers
44287
// A value of this type can a JSON-RPC request, notification, successful response or
45288
// error response. Which one it is depends on the fields.
@@ -65,7 +308,7 @@ type jsonrpcMessage struct {
65308
Result json.RawMessage `json:"result,omitempty"`
66309
}
67310

68-
func UnmarshalFromJsonRPC(b []byte, result interface{}) error {
311+
func UnmarshalFromJsonRPCResponse(b []byte, result interface{}) error {
69312
var rpcMessage jsonrpcMessage
70313
err := json.Unmarshal(b, &rpcMessage)
71314
if err != nil {
@@ -77,3 +320,16 @@ func UnmarshalFromJsonRPC(b []byte, result interface{}) error {
77320
err = json.Unmarshal(rpcMessage.Result, &result)
78321
return nil
79322
}
323+
324+
func UnmarshalFromJsonRPCRequest(b []byte, params ...interface{}) error {
325+
var rpcMessage jsonrpcMessage
326+
err := json.Unmarshal(b, &rpcMessage)
327+
if err != nil {
328+
return err
329+
}
330+
if rpcMessage.Error != nil {
331+
return rpcMessage.Error
332+
}
333+
err = json.Unmarshal(rpcMessage.Params, &params)
334+
return nil
335+
}

simulators/eth2/engine/gen_blockparams.go

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

simulators/eth2/engine/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,5 @@ require (
7070
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
7171
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
7272
)
73+
74+
replace github.com/rauljordan/engine-proxy => github.com/marioevz/engine-proxy v0.0.0-20220617181151-e8661eb39eea

simulators/eth2/engine/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2
272272
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
273273
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
274274
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
275+
github.com/marioevz/engine-proxy v0.0.0-20220617181151-e8661eb39eea h1:9ahHMPkNvYf9Nn3+U072ZKMDm05gaXfRESAmwP07Q+M=
276+
github.com/marioevz/engine-proxy v0.0.0-20220617181151-e8661eb39eea/go.mod h1:9OVXfWYnIV+wj1/SqfdREmE5mzN/OANAgdOJRtFtvpo=
275277
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
276278
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
277279
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=

0 commit comments

Comments
 (0)