Skip to content

Commit 976d36b

Browse files
marioevzzilm13
andauthored
simulators/eth2/engine: Update engine-proxy/hive, Refactor, Add Test Cases (#569)
* simulators/eth2/engine: Update proxy, add test cases * simulators/eth2/engine: Add test cases * clients/teku: add archive data storage param * simulators/eth2/engine: small fixes for Teku * simulators/eth2/engine: Spoof ForkchoiceUpdated in tests * simulators/eth2/engine: fix proxies cleanup * simulators/eth2/engine: add test cases * simulators/eth2/engine: add invalid timestamp test * simulators/eth2/engine: accomodate different client configs * simulators/eth2/engine: incorrect terminal block test fixes * clients/teku: add startup target peer count parameter Co-authored-by: Dmitry S. <zilm13@users.noreply.github.com> * simulators/eth2/engine: el + cl ttd settings * clients/lighthouse: parse TTD hive parameter * clients/teku: parse TTD hive parameter * simulators/eth2/engine: fix invalid-quantity-fields * simulators/eth2/engine: skip 0 validator client * simulators/eth2/engine: Refactor invalid payload tests * simulators/eth2/engine: improve invalid quantity test * simulators/eth2/engine: fix syncing with inv chain * simulators/eth2/engine: Refactor verifications * simulators/eth2/engine: increase ttd for incremental forks config * simulators/eth2/engine: print TTD reached while waiting for exec payload * simulators/eth2/engine: update hive * simulators/eth2/engine: Refactor to add tests to suite Co-authored-by: Dmitry S. <zilm13@users.noreply.github.com>
1 parent 63385ea commit 976d36b

File tree

20 files changed

+3830
-267
lines changed

20 files changed

+3830
-267
lines changed

clients/lighthouse-bn/lighthouse_bn.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ mkdir -p /data/testnet_setup
1616
cp /hive/input/genesis.ssz /data/testnet_setup/genesis.ssz
1717
cp /hive/input/config.yaml /data/testnet_setup
1818

19+
if [ "$HIVE_TERMINAL_TOTAL_DIFFICULTY" != "" ]; then
20+
sed -i '/TERMINAL_TOTAL_DIFFICULTY/d' /data/testnet_setup/config.yaml
21+
echo "TERMINAL_TOTAL_DIFFICULTY: $HIVE_TERMINAL_TOTAL_DIFFICULTY" >> /data/testnet_setup/config.yaml
22+
fi
23+
1924
# empty bootnodes file, required for custom testnet setup, use CLI arg instead to configure it.
2025
echo "[]" > /data/testnet_setup/boot_enr.yaml
2126

clients/teku-bn/teku_bn.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ mkdir -p /data/testnet_setup
1515
cp /hive/input/genesis.ssz /data/testnet_setup/genesis.ssz
1616
cp /hive/input/config.yaml /data/testnet_setup
1717

18+
if [ "$HIVE_TERMINAL_TOTAL_DIFFICULTY" != "" ]; then
19+
sed -i '/TERMINAL_TOTAL_DIFFICULTY/d' /data/testnet_setup/config.yaml
20+
echo "TERMINAL_TOTAL_DIFFICULTY: $HIVE_TERMINAL_TOTAL_DIFFICULTY" >> /data/testnet_setup/config.yaml
21+
fi
22+
1823
mkdir -p /data/teku
1924

2025
LOG=INFO
@@ -38,6 +43,8 @@ if [ "$HIVE_ETH2_MERGE_ENABLED" != "" ]; then
3843
merge_option="--ee-endpoint=$HIVE_ETH2_ETH1_ENGINE_RPC_ADDRS --ee-jwt-secret-file=/jwtsecret"
3944
fi
4045

46+
echo Starting Teku Beacon Node
47+
4148
/opt/teku/bin/teku \
4249
--network=/data/testnet_setup/config.yaml \
4350
--data-path=/data/teku \
@@ -51,4 +58,6 @@ fi
5158
--p2p-udp-port="${HIVE_ETH2_P2P_UDP_PORT:-9000}" \
5259
--p2p-advertised-ip="${CONTAINER_IP}" \
5360
--p2p-peer-lower-bound="${HIVE_ETH2_P2P_TARGET_PEERS:-10}" \
54-
--rest-api-enabled=true --rest-api-interface=0.0.0.0 --rest-api-port="${HIVE_ETH2_BN_API_PORT:-4000}" --rest-api-host-allowlist="*"
61+
--rest-api-enabled=true --rest-api-interface=0.0.0.0 --rest-api-port="${HIVE_ETH2_BN_API_PORT:-4000}" --rest-api-host-allowlist="*" \
62+
--data-storage-mode=ARCHIVE \
63+
--Xstartup-target-peer-count=0

clients/teku-vc/teku_vc.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ case "$HIVE_LOGLEVEL" in
2424
5) LOG=TRACE ;;
2525
esac
2626

27+
echo Starting Teku Validator Client
28+
2729
/opt/teku/bin/teku vc \
2830
--network=auto \
2931
--beacon-node-api-endpoint="http://$HIVE_ETH2_BN_API_IP:$HIVE_ETH2_BN_API_PORT" \
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"encoding/json"
7+
"fmt"
8+
"math/big"
9+
"net"
10+
"net/http"
11+
"time"
12+
13+
"github.com/ethereum/go-ethereum/common"
14+
"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"
20+
)
21+
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+
// API call names
27+
const (
28+
EngineForkchoiceUpdatedV1 string = "engine_forkchoiceUpdatedV1"
29+
EngineGetPayloadV1 = "engine_getPayloadV1"
30+
EngineNewPayloadV1 = "engine_newPayloadV1"
31+
)
32+
33+
// EngineClient wrapper for Ethereum Engine RPC for testing purposes.
34+
type EngineClient struct {
35+
*hivesim.T
36+
*hivesim.Client
37+
c *rpc.Client
38+
Eth *ethclient.Client
39+
cEth *rpc.Client
40+
IP net.IP
41+
TerminalTotalDifficulty *big.Int
42+
43+
// This holds most recent context created by the Ctx method.
44+
// Every time Ctx is called, it creates a new context with the default
45+
// timeout and cancels the previous one.
46+
lastCtx context.Context
47+
lastCancel context.CancelFunc
48+
}
49+
50+
// NewClient creates a engine client that uses the given RPC client.
51+
func NewEngineClient(t *hivesim.T, n *Eth1Node, ttd *big.Int) *EngineClient {
52+
engineRPCAddress, err := n.EngineRPCAddress()
53+
if err != nil {
54+
panic(err)
55+
}
56+
57+
client := &http.Client{}
58+
// Prepare HTTP Client
59+
rpcHttpClient, _ := rpc.DialHTTPWithClient(engineRPCAddress, client)
60+
61+
// Prepare ETH Client
62+
client = &http.Client{}
63+
64+
userRPCAddress, err := n.UserRPCAddress()
65+
if err != nil {
66+
panic(err)
67+
}
68+
rpcClient, _ := rpc.DialHTTPWithClient(userRPCAddress, client)
69+
eth := ethclient.NewClient(rpcClient)
70+
return &EngineClient{
71+
T: t,
72+
c: rpcHttpClient,
73+
Eth: eth,
74+
cEth: rpcClient,
75+
TerminalTotalDifficulty: ttd,
76+
lastCtx: nil,
77+
lastCancel: nil,
78+
}
79+
}
80+
81+
func (ec *EngineClient) Close() {
82+
ec.c.Close()
83+
ec.Eth.Close()
84+
if ec.lastCtx != nil {
85+
ec.lastCancel()
86+
}
87+
}
88+
89+
func (ec *EngineClient) Ctx() context.Context {
90+
if ec.lastCtx != nil {
91+
ec.lastCancel()
92+
}
93+
ec.lastCtx, ec.lastCancel = context.WithTimeout(context.Background(), 10*time.Second)
94+
return ec.lastCtx
95+
}
96+
97+
// Helper structs to fetch the TotalDifficulty
98+
type TD struct {
99+
TotalDifficulty *hexutil.Big `json:"totalDifficulty"`
100+
}
101+
type TotalDifficultyHeader struct {
102+
types.Header
103+
TD
104+
}
105+
106+
func (tdh *TotalDifficultyHeader) UnmarshalJSON(data []byte) error {
107+
if err := json.Unmarshal(data, &tdh.Header); err != nil {
108+
return err
109+
}
110+
if err := json.Unmarshal(data, &tdh.TD); err != nil {
111+
return err
112+
}
113+
return nil
114+
}
115+
116+
func (ec *EngineClient) checkTTD() (*types.Header, bool) {
117+
var td *TotalDifficultyHeader
118+
if err := ec.cEth.CallContext(ec.Ctx(), &td, "eth_getBlockByNumber", "latest", false); err != nil {
119+
panic(err)
120+
}
121+
if td.TotalDifficulty.ToInt().Cmp(ec.TerminalTotalDifficulty) >= 0 {
122+
return &td.Header, true
123+
}
124+
return nil, false
125+
}
126+
127+
// Engine API Types
128+
type PayloadStatusV1 struct {
129+
Status PayloadStatus `json:"status"`
130+
LatestValidHash *common.Hash `json:"latestValidHash"`
131+
ValidationError *string `json:"validationError"`
132+
}
133+
type ForkChoiceResponse struct {
134+
PayloadStatus PayloadStatusV1 `json:"payloadStatus"`
135+
PayloadID *PayloadID `json:"payloadId"`
136+
}
137+
138+
type PayloadStatus int
139+
140+
const (
141+
Unknown PayloadStatus = iota
142+
Valid
143+
Invalid
144+
Accepted
145+
Syncing
146+
InvalidBlockHash
147+
)
148+
149+
var PayloadStatuses = map[PayloadStatus]string{
150+
Valid: "VALID",
151+
Invalid: "INVALID",
152+
Accepted: "ACCEPTED",
153+
Syncing: "SYNCING",
154+
InvalidBlockHash: "INVALID_BLOCK_HASH",
155+
}
156+
157+
func (b PayloadStatus) String() string {
158+
str, ok := PayloadStatuses[b]
159+
if !ok {
160+
return "UNKNOWN"
161+
}
162+
return str
163+
}
164+
165+
func (b PayloadStatus) MarshalText() ([]byte, error) {
166+
str, ok := PayloadStatuses[b]
167+
if !ok {
168+
return nil, fmt.Errorf("invalid payload status")
169+
}
170+
return []byte(str), nil
171+
}
172+
173+
func (b *PayloadStatus) UnmarshalText(input []byte) error {
174+
s := string(input)
175+
for p, status := range PayloadStatuses {
176+
if status == s {
177+
*b = p
178+
return nil
179+
}
180+
}
181+
return fmt.Errorf("invalid payload status: %s", s)
182+
}
183+
184+
type PayloadID [8]byte
185+
186+
func (b PayloadID) MarshalText() ([]byte, error) {
187+
return hexutil.Bytes(b[:]).MarshalText()
188+
}
189+
func (b *PayloadID) UnmarshalText(input []byte) error {
190+
err := hexutil.UnmarshalFixedText("PayloadID", input, b[:])
191+
if err != nil {
192+
return fmt.Errorf("invalid payload id %q: %w", input, err)
193+
}
194+
return nil
195+
}
196+
197+
type ForkchoiceStateV1 struct {
198+
HeadBlockHash common.Hash `json:"headBlockHash"`
199+
SafeBlockHash common.Hash `json:"safeBlockHash"`
200+
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
201+
}
202+
203+
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
204+
// ExecutableDataV1 structure described at https://github.com/ethereum/execution-apis/src/engine/specification.md
205+
type ExecutableDataV1 struct {
206+
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
207+
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
208+
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
209+
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
210+
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
211+
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
212+
Number uint64 `json:"blockNumber" gencodec:"required"`
213+
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
214+
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
215+
Timestamp uint64 `json:"timestamp" gencodec:"required"`
216+
ExtraData []byte `json:"extraData" gencodec:"required"`
217+
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
218+
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
219+
Transactions [][]byte `json:"transactions" gencodec:"required"`
220+
}
221+
222+
// JSON type overrides for executableData.
223+
type executableDataMarshaling struct {
224+
Number hexutil.Uint64
225+
GasLimit hexutil.Uint64
226+
GasUsed hexutil.Uint64
227+
Timestamp hexutil.Uint64
228+
BaseFeePerGas *hexutil.Big
229+
ExtraData hexutil.Bytes
230+
LogsBloom hexutil.Bytes
231+
Transactions []hexutil.Bytes
232+
}
233+
234+
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
235+
// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74
236+
type PayloadAttributesV1 struct {
237+
Timestamp uint64 `json:"timestamp" gencodec:"required"`
238+
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
239+
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
240+
}
241+
242+
// JSON type overrides for PayloadAttributesV1.
243+
type payloadAttributesMarshaling struct {
244+
Timestamp hexutil.Uint64
245+
}
246+
247+
// JWT Tokens
248+
func GetNewToken(jwtSecretBytes []byte, iat time.Time) (string, error) {
249+
newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
250+
"iat": iat.Unix(),
251+
})
252+
tokenString, err := newToken.SignedString(jwtSecretBytes)
253+
if err != nil {
254+
return "", err
255+
}
256+
return tokenString, nil
257+
}
258+
259+
func (ec *EngineClient) PrepareAuthCallToken(jwtSecretBytes []byte, iat time.Time) error {
260+
newTokenString, err := GetNewToken(jwtSecretBytes, iat)
261+
if err != nil {
262+
return err
263+
}
264+
ec.c.SetHeader("Authorization", fmt.Sprintf("Bearer %s", newTokenString))
265+
return nil
266+
}
267+
268+
func (ec *EngineClient) PrepareDefaultAuthCallToken() error {
269+
secret, _ := hex.DecodeString("7365637265747365637265747365637265747365637265747365637265747365")
270+
ec.PrepareAuthCallToken(secret, time.Now())
271+
return nil
272+
}
273+
274+
// Engine API Call Methods
275+
func (ec *EngineClient) EngineForkchoiceUpdatedV1(fcState *ForkchoiceStateV1, pAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
276+
var result ForkChoiceResponse
277+
if err := ec.PrepareDefaultAuthCallToken(); err != nil {
278+
return result, err
279+
}
280+
err := ec.c.CallContext(ec.Ctx(), &result, EngineForkchoiceUpdatedV1, fcState, pAttributes)
281+
return result, err
282+
}
283+
284+
func (ec *EngineClient) EngineGetPayloadV1(payloadId *PayloadID) (ExecutableDataV1, error) {
285+
var result ExecutableDataV1
286+
if err := ec.PrepareDefaultAuthCallToken(); err != nil {
287+
return result, err
288+
}
289+
err := ec.c.CallContext(ec.Ctx(), &result, EngineGetPayloadV1, payloadId)
290+
return result, err
291+
}
292+
293+
func (ec *EngineClient) EngineNewPayloadV1(payload *ExecutableDataV1) (PayloadStatusV1, error) {
294+
var result PayloadStatusV1
295+
if err := ec.PrepareDefaultAuthCallToken(); err != nil {
296+
return result, err
297+
}
298+
err := ec.c.CallContext(ec.Ctx(), &result, EngineNewPayloadV1, payload)
299+
return result, err
300+
}
301+
302+
// Json helpers
303+
// A value of this type can a JSON-RPC request, notification, successful response or
304+
// error response. Which one it is depends on the fields.
305+
type jsonError struct {
306+
Code int `json:"code"`
307+
Message string `json:"message"`
308+
Data interface{} `json:"data,omitempty"`
309+
}
310+
311+
func (err *jsonError) Error() string {
312+
if err.Message == "" {
313+
return fmt.Sprintf("json-rpc error %d", err.Code)
314+
}
315+
return err.Message
316+
}
317+
318+
type jsonrpcMessage struct {
319+
Version string `json:"jsonrpc,omitempty"`
320+
ID json.RawMessage `json:"id,omitempty"`
321+
Method string `json:"method,omitempty"`
322+
Params json.RawMessage `json:"params,omitempty"`
323+
Error *jsonError `json:"error,omitempty"`
324+
Result json.RawMessage `json:"result,omitempty"`
325+
}
326+
327+
func UnmarshalFromJsonRPCResponse(b []byte, result interface{}) error {
328+
var rpcMessage jsonrpcMessage
329+
err := json.Unmarshal(b, &rpcMessage)
330+
if err != nil {
331+
return err
332+
}
333+
if rpcMessage.Error != nil {
334+
return rpcMessage.Error
335+
}
336+
return json.Unmarshal(rpcMessage.Result, &result)
337+
}
338+
339+
func UnmarshalFromJsonRPCRequest(b []byte, params ...interface{}) error {
340+
var rpcMessage jsonrpcMessage
341+
err := json.Unmarshal(b, &rpcMessage)
342+
if err != nil {
343+
return err
344+
}
345+
if rpcMessage.Error != nil {
346+
return rpcMessage.Error
347+
}
348+
return json.Unmarshal(rpcMessage.Params, &params)
349+
}

0 commit comments

Comments
 (0)