From 045a3cfe4e91629a021cd03986586306a6a602da Mon Sep 17 00:00:00 2001 From: Bastin <43618253+Inspector-Butters@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:02:06 +0100 Subject: [PATCH] GetVersionV2 endpoint (#16347) **What does this PR do? Why is it needed?** This PR adds the implementation for `GetVersionV2` based on [ethereum/beacon-APIs#568](https://github.com/ethereum/beacon-APIs/pull/568). Q: is there anything to be done for deprecating `GetVersionV1`? --- api/server/structs/endpoints_node.go | 13 +++ beacon-chain/execution/BUILD.bazel | 2 + beacon-chain/execution/engine_client.go | 37 ++++++++ beacon-chain/execution/engine_client_test.go | 94 +++++++++++++++++++ beacon-chain/execution/testing/BUILD.bazel | 1 + .../execution/testing/mock_engine_client.go | 8 ++ beacon-chain/rpc/endpoints.go | 11 +++ beacon-chain/rpc/endpoints_test.go | 1 + beacon-chain/rpc/eth/node/BUILD.bazel | 3 + beacon-chain/rpc/eth/node/handlers.go | 34 +++++++ beacon-chain/rpc/eth/node/handlers_test.go | 70 ++++++++++++++ beacon-chain/rpc/eth/node/log.go | 9 ++ beacon-chain/rpc/eth/node/server.go | 1 + changelog/bastin_get-version-v2.md | 3 + runtime/version/version.go | 11 ++- .../shared/common/forkchoice/BUILD.bazel | 1 + .../shared/common/forkchoice/service.go | 5 + 17 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 beacon-chain/rpc/eth/node/log.go create mode 100644 changelog/bastin_get-version-v2.md diff --git a/api/server/structs/endpoints_node.go b/api/server/structs/endpoints_node.go index 58b00fdf3f..80361e4ad8 100644 --- a/api/server/structs/endpoints_node.go +++ b/api/server/structs/endpoints_node.go @@ -63,6 +63,19 @@ type PeerCount struct { Connected string `json:"connected"` Disconnecting string `json:"disconnecting"` } +type GetVersionV2Response struct { + Data *VersionV2 `json:"data"` +} +type VersionV2 struct { + BeaconNode *ClientVersionV1 `json:"beacon_node"` + ExecutionClient *ClientVersionV1 `json:"execution_client,omitempty"` +} +type ClientVersionV1 struct { + Code string `json:"code"` + Name string `json:"name"` + Version string `json:"version"` + Commit string `json:"commit"` +} type GetVersionResponse struct { Data *Version `json:"data"` diff --git a/beacon-chain/execution/BUILD.bazel b/beacon-chain/execution/BUILD.bazel index 1f0d20c465..985d8ef6f8 100644 --- a/beacon-chain/execution/BUILD.bazel +++ b/beacon-chain/execution/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//testing/spectest:__subpackages__", ], deps = [ + "//api/server/structs:go_default_library", "//beacon-chain/blockchain/kzg:go_default_library", "//beacon-chain/cache:go_default_library", "//beacon-chain/cache/depositsnapshot:go_default_library", @@ -101,6 +102,7 @@ go_test( data = glob(["testdata/**"]), embed = [":go_default_library"], deps = [ + "//api/server/structs:go_default_library", "//async/event:go_default_library", "//beacon-chain/blockchain/kzg:go_default_library", "//beacon-chain/blockchain/testing:go_default_library", diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index 7d35c59508..a5c55b66d3 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/OffchainLabs/prysm/v7/api/server/structs" "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg" "github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas" "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/types" @@ -109,6 +110,8 @@ const ( GetBlobsV1 = "engine_getBlobsV1" // GetBlobsV2 request string for JSON-RPC. GetBlobsV2 = "engine_getBlobsV2" + // GetClientVersionV1 is the JSON-RPC method that identifies the execution client. + GetClientVersionV1 = "engine_getClientVersionV1" // Defines the seconds before timing out engine endpoints with non-block execution semantics. defaultEngineTimeout = time.Second ) @@ -145,6 +148,7 @@ type EngineCaller interface { GetPayload(ctx context.Context, payloadId [8]byte, slot primitives.Slot) (*blocks.GetPayloadResponse, error) ExecutionBlockByHash(ctx context.Context, hash common.Hash, withTxs bool) (*pb.ExecutionBlock, error) GetTerminalBlockHash(ctx context.Context, transitionTime uint64) ([]byte, bool, error) + GetClientVersionV1(ctx context.Context) ([]*structs.ClientVersionV1, error) } var ErrEmptyBlockHash = errors.New("Block hash is empty 0x0000...") @@ -581,6 +585,39 @@ func (s *Service) GetBlobsV2(ctx context.Context, versionedHashes []common.Hash) return result, handleRPCError(err) } +func (s *Service) GetClientVersionV1(ctx context.Context) ([]*structs.ClientVersionV1, error) { + ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.GetClientVersionV1") + defer span.End() + + commit := version.GitCommit() + if len(commit) >= 8 { + commit = commit[:8] + } + + var result []*structs.ClientVersionV1 + err := s.rpcClient.CallContext( + ctx, + &result, + GetClientVersionV1, + structs.ClientVersionV1{ + Code: "PM", + Name: "Prysm", + Version: version.SemanticVersion(), + Commit: commit, + }, + ) + + if err != nil { + return nil, handleRPCError(err) + } + + if len(result) == 0 { + return nil, errors.New("execution client returned no result") + } + + return result, nil +} + // ReconstructFullBlock takes in a blinded beacon block and reconstructs // a beacon block with a full execution payload via the engine API. func (s *Service) ReconstructFullBlock( diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go index 1d15e76197..ae85922909 100644 --- a/beacon-chain/execution/engine_client_test.go +++ b/beacon-chain/execution/engine_client_test.go @@ -13,6 +13,7 @@ import ( "strings" "testing" + "github.com/OffchainLabs/prysm/v7/api/server/structs" "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg" "github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas" "github.com/OffchainLabs/prysm/v7/beacon-chain/db/filesystem" @@ -999,6 +1000,99 @@ func TestClient_HTTP(t *testing.T) { require.NoError(t, err) require.DeepEqual(t, want, resp) }) + t.Run(GetClientVersionV1, func(t *testing.T) { + tests := []struct { + name string + want any + resp []*structs.ClientVersionV1 + hasError bool + errMsg string + }{ + { + name: "happy path", + want: []*structs.ClientVersionV1{{ + Code: "GE", + Name: "go-ethereum", + Version: "1.15.11-stable", + Commit: "36b2371c", + }}, + resp: []*structs.ClientVersionV1{{ + Code: "GE", + Name: "go-ethereum", + Version: "1.15.11-stable", + Commit: "36b2371c", + }}, + }, + { + name: "empty response", + want: []*structs.ClientVersionV1{}, + hasError: true, + errMsg: "execution client returned no result", + }, + { + name: "RPC error", + want: "brokenMsg", + hasError: true, + errMsg: "unexpected error in JSON-RPC", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + defer func() { + require.NoError(t, r.Body.Close()) + }() + enc, err := io.ReadAll(r.Body) + require.NoError(t, err) + jsonRequestString := string(enc) + + // We expect the JSON string RPC request contains the right method name. + require.Equal(t, true, strings.Contains( + jsonRequestString, GetClientVersionV1, + )) + require.Equal(t, true, strings.Contains( + jsonRequestString, "\"code\":\"PM\"", + )) + require.Equal(t, true, strings.Contains( + jsonRequestString, "\"name\":\"Prysm\"", + )) + require.Equal(t, true, strings.Contains( + jsonRequestString, fmt.Sprintf("\"version\":\"%s\"", version.SemanticVersion()), + )) + require.Equal(t, true, strings.Contains( + jsonRequestString, fmt.Sprintf("\"commit\":\"%s\"", version.GitCommit()[:8]), + )) + resp := map[string]any{ + "jsonrpc": "2.0", + "id": 1, + "result": tc.want, + } + err = json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + })) + defer srv.Close() + + rpcClient, err := rpc.DialHTTP(srv.URL) + require.NoError(t, err) + defer rpcClient.Close() + + service := &Service{} + service.rpcClient = rpcClient + + // We call the RPC method via HTTP and expect a proper result. + resp, err := service.GetClientVersionV1(ctx) + if tc.hasError { + require.NotNil(t, err) + require.ErrorContains(t, tc.errMsg, err) + } else { + require.NoError(t, err) + } + require.DeepEqual(t, tc.resp, resp) + }) + } + }) } func TestReconstructFullBellatrixBlock(t *testing.T) { diff --git a/beacon-chain/execution/testing/BUILD.bazel b/beacon-chain/execution/testing/BUILD.bazel index f1e09ecb4f..5a7048c886 100644 --- a/beacon-chain/execution/testing/BUILD.bazel +++ b/beacon-chain/execution/testing/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//visibility:public", ], deps = [ + "//api/server/structs:go_default_library", "//async/event:go_default_library", "//beacon-chain/core/peerdas:go_default_library", "//beacon-chain/execution/types:go_default_library", diff --git a/beacon-chain/execution/testing/mock_engine_client.go b/beacon-chain/execution/testing/mock_engine_client.go index 9f3d66f1e2..f6e4a7892e 100644 --- a/beacon-chain/execution/testing/mock_engine_client.go +++ b/beacon-chain/execution/testing/mock_engine_client.go @@ -4,6 +4,7 @@ import ( "context" "math/big" + "github.com/OffchainLabs/prysm/v7/api/server/structs" "github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas" fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams" "github.com/OffchainLabs/prysm/v7/config/params" @@ -42,6 +43,8 @@ type EngineClient struct { ErrorBlobSidecars error DataColumnSidecars []blocks.VerifiedRODataColumn ErrorDataColumnSidecars error + ClientVersion []*structs.ClientVersionV1 + ErrorClientVersion error } // NewPayload -- @@ -173,3 +176,8 @@ func (e *EngineClient) GetTerminalBlockHash(ctx context.Context, transitionTime blk = parentBlk } } + +// GetClientVersionV1 -- +func (e *EngineClient) GetClientVersionV1(context.Context) ([]*structs.ClientVersionV1, error) { + return e.ClientVersion, e.ErrorClientVersion +} diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index 4b953a1b07..b1f3c8b544 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -405,6 +405,7 @@ func (s *Service) nodeEndpoints() []endpoint { MetadataProvider: s.cfg.MetadataProvider, HeadFetcher: s.cfg.HeadFetcher, ExecutionChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher, + ExecutionEngineCaller: s.cfg.ExecutionEngineCaller, } const namespace = "node" @@ -469,6 +470,16 @@ func (s *Service) nodeEndpoints() []endpoint { handler: server.GetVersion, methods: []string{http.MethodGet}, }, + { + template: "/eth/v2/node/version", + name: namespace + ".GetVersionV2", + middleware: []middleware.Middleware{ + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + middleware.AcceptEncodingHeaderHandler(), + }, + handler: server.GetVersionV2, + methods: []string{http.MethodGet}, + }, { template: "/eth/v1/node/health", name: namespace + ".GetHealth", diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index 7967e3c592..59f7572b2b 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -86,6 +86,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/node/peers/{peer_id}": {http.MethodGet}, "/eth/v1/node/peer_count": {http.MethodGet}, "/eth/v1/node/version": {http.MethodGet}, + "/eth/v2/node/version": {http.MethodGet}, "/eth/v1/node/syncing": {http.MethodGet}, "/eth/v1/node/health": {http.MethodGet}, } diff --git a/beacon-chain/rpc/eth/node/BUILD.bazel b/beacon-chain/rpc/eth/node/BUILD.bazel index bb409c78e8..e231f8b868 100644 --- a/beacon-chain/rpc/eth/node/BUILD.bazel +++ b/beacon-chain/rpc/eth/node/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "handlers.go", "handlers_peers.go", + "log.go", "server.go", ], importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/eth/node", @@ -30,6 +31,7 @@ go_library( "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_libp2p_go_libp2p//core/peer:go_default_library", "@com_github_pkg_errors//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", "@org_golang_google_grpc//:go_default_library", ], ) @@ -44,6 +46,7 @@ go_test( deps = [ "//api/server/structs:go_default_library", "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/execution/testing:go_default_library", "//beacon-chain/p2p:go_default_library", "//beacon-chain/p2p/peers:go_default_library", "//beacon-chain/p2p/testing:go_default_library", diff --git a/beacon-chain/rpc/eth/node/handlers.go b/beacon-chain/rpc/eth/node/handlers.go index 01d979c0b5..5c29313293 100644 --- a/beacon-chain/rpc/eth/node/handlers.go +++ b/beacon-chain/rpc/eth/node/handlers.go @@ -103,6 +103,8 @@ func (s *Server) GetIdentity(w http.ResponseWriter, r *http.Request) { // GetVersion requests that the beacon node identify information about its implementation in a // format similar to a HTTP User-Agent field. +// +// Deprecated: in favour of GetVersionV2. func (*Server) GetVersion(w http.ResponseWriter, r *http.Request) { _, span := trace.StartSpan(r.Context(), "node.GetVersion") defer span.End() @@ -116,6 +118,38 @@ func (*Server) GetVersion(w http.ResponseWriter, r *http.Request) { httputil.WriteJson(w, resp) } +// GetVersionV2 Retrieves structured information about the version of the beacon node and its attached +// execution client in the same format as used on the Engine API +func (s *Server) GetVersionV2(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "node.GetVersionV2") + defer span.End() + + var elData *structs.ClientVersionV1 + elDataList, err := s.ExecutionEngineCaller.GetClientVersionV1(ctx) + if err != nil { + log.WithError(err).WithField("endpoint", "GetVersionV2").Debug("Could not get execution client version") + } else if len(elDataList) > 0 { + elData = elDataList[0] + } + + commit := version.GitCommit() + if len(commit) >= 8 { + commit = commit[:8] + } + resp := &structs.GetVersionV2Response{ + Data: &structs.VersionV2{ + BeaconNode: &structs.ClientVersionV1{ + Code: "PM", + Name: "Prysm", + Version: version.SemanticVersion(), + Commit: commit, + }, + ExecutionClient: elData, + }, + } + httputil.WriteJson(w, resp) +} + // GetHealth returns node health status in http status codes. Useful for load balancers. func (s *Server) GetHealth(w http.ResponseWriter, r *http.Request) { ctx, span := trace.StartSpan(r.Context(), "node.GetHealth") diff --git a/beacon-chain/rpc/eth/node/handlers_test.go b/beacon-chain/rpc/eth/node/handlers_test.go index 4c5e70e548..be886201ef 100644 --- a/beacon-chain/rpc/eth/node/handlers_test.go +++ b/beacon-chain/rpc/eth/node/handlers_test.go @@ -12,6 +12,7 @@ import ( "github.com/OffchainLabs/go-bitfield" "github.com/OffchainLabs/prysm/v7/api/server/structs" mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing" + mockengine "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing" "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p" mockp2p "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing" "github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/testutil" @@ -90,6 +91,75 @@ func TestGetVersion(t *testing.T) { assert.StringContains(t, arch, resp.Data.Version) } +func TestGetVersionV2(t *testing.T) { + t.Run("happy path", func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/node/version", nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s := &Server{ + ExecutionEngineCaller: &mockengine.EngineClient{ + ClientVersion: []*structs.ClientVersionV1{{ + Code: "EL", + Name: "ExecutionClient", + Version: "v1.0.0", + Commit: "abcdef12", + }}, + }, + } + s.GetVersionV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.GetVersionV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + require.NotNil(t, resp.Data.BeaconNode) + require.NotNil(t, resp.Data.ExecutionClient) + require.Equal(t, "EL", resp.Data.ExecutionClient.Code) + require.Equal(t, "ExecutionClient", resp.Data.ExecutionClient.Name) + require.Equal(t, "v1.0.0", resp.Data.ExecutionClient.Version) + require.Equal(t, "abcdef12", resp.Data.ExecutionClient.Commit) + require.Equal(t, "PM", resp.Data.BeaconNode.Code) + require.Equal(t, "Prysm", resp.Data.BeaconNode.Name) + require.Equal(t, version.SemanticVersion(), resp.Data.BeaconNode.Version) + require.Equal(t, true, len(resp.Data.BeaconNode.Commit) <= 8) + }) + + t.Run("unhappy path", func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/node/version", nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s := &Server{ + ExecutionEngineCaller: &mockengine.EngineClient{ + ClientVersion: nil, + ErrorClientVersion: fmt.Errorf("error"), + }, + } + s.GetVersionV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.GetVersionV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + require.NotNil(t, resp.Data.BeaconNode) + require.Equal(t, true, resp.Data.ExecutionClient == nil) + + // make sure there is no 'execution_client' field + var payload map[string]any + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &payload)) + data, ok := payload["data"].(map[string]any) + require.Equal(t, true, ok) + _, found := data["beacon_node"] + require.Equal(t, true, found) + _, found = data["execution_client"] + require.Equal(t, false, found) + }) + +} + func TestGetHealth(t *testing.T) { checker := &syncmock.Sync{} optimisticFetcher := &mock.ChainService{Optimistic: false} diff --git a/beacon-chain/rpc/eth/node/log.go b/beacon-chain/rpc/eth/node/log.go new file mode 100644 index 0000000000..13dcbd6e19 --- /dev/null +++ b/beacon-chain/rpc/eth/node/log.go @@ -0,0 +1,9 @@ +// Code generated by hack/gen-logs.sh; DO NOT EDIT. +// This file is created and regenerated automatically. Anything added here might get removed. +package node + +import "github.com/sirupsen/logrus" + +// The prefix for logs from this package will be the text after the last slash in the package path. +// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file. +var log = logrus.WithField("package", "beacon-chain/rpc/eth/node") diff --git a/beacon-chain/rpc/eth/node/server.go b/beacon-chain/rpc/eth/node/server.go index 588bcef841..74ec04b787 100644 --- a/beacon-chain/rpc/eth/node/server.go +++ b/beacon-chain/rpc/eth/node/server.go @@ -26,4 +26,5 @@ type Server struct { GenesisTimeFetcher blockchain.TimeFetcher HeadFetcher blockchain.HeadFetcher ExecutionChainInfoFetcher execution.ChainInfoFetcher + ExecutionEngineCaller execution.EngineCaller } diff --git a/changelog/bastin_get-version-v2.md b/changelog/bastin_get-version-v2.md new file mode 100644 index 0000000000..9460285703 --- /dev/null +++ b/changelog/bastin_get-version-v2.md @@ -0,0 +1,3 @@ +### Added + +- New beacon API endpoint `eth/v2/node/version`. \ No newline at end of file diff --git a/runtime/version/version.go b/runtime/version/version.go index 7014950171..d16a845a8c 100644 --- a/runtime/version/version.go +++ b/runtime/version/version.go @@ -34,8 +34,17 @@ func SemanticVersion() string { return gitTag } +// GitCommit returns the current build commit hash. +func GitCommit() string { + return resolvedGitCommit() +} + // BuildData returns the git tag and commit of the current build. func BuildData() string { + return fmt.Sprintf("Prysm/%s/%s", gitTag, resolvedGitCommit()) +} + +func resolvedGitCommit() string { // if doing a local build, these values are not interpolated if gitCommit == "{STABLE_GIT_COMMIT}" { commit, err := exec.Command("git", "rev-parse", "HEAD").Output() @@ -45,7 +54,7 @@ func BuildData() string { gitCommit = strings.TrimRight(string(commit), "\r\n") } } - return fmt.Sprintf("Prysm/%s/%s", gitTag, gitCommit) + return gitCommit } // GetCommitPrefix returns the first 4 characters of the git commit. diff --git a/testing/spectest/shared/common/forkchoice/BUILD.bazel b/testing/spectest/shared/common/forkchoice/BUILD.bazel index 8d0aa99e4a..255e0b8a61 100644 --- a/testing/spectest/shared/common/forkchoice/BUILD.bazel +++ b/testing/spectest/shared/common/forkchoice/BUILD.bazel @@ -12,6 +12,7 @@ go_library( importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/common/forkchoice", visibility = ["//testing/spectest:__subpackages__"], deps = [ + "//api/server/structs:go_default_library", "//beacon-chain/blockchain:go_default_library", "//beacon-chain/blockchain/kzg:go_default_library", "//beacon-chain/blockchain/testing:go_default_library", diff --git a/testing/spectest/shared/common/forkchoice/service.go b/testing/spectest/shared/common/forkchoice/service.go index d96b8e600c..e7ee1fb5f9 100644 --- a/testing/spectest/shared/common/forkchoice/service.go +++ b/testing/spectest/shared/common/forkchoice/service.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/OffchainLabs/prysm/v7/api/server/structs" "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain" "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg" mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing" @@ -141,3 +142,7 @@ func (m *engineMock) ExecutionBlockByHash(_ context.Context, hash common.Hash, _ func (m *engineMock) GetTerminalBlockHash(context.Context, uint64) ([]byte, bool, error) { return nil, false, nil } + +func (m *engineMock) GetClientVersionV1(context.Context) ([]*structs.ClientVersionV1, error) { + return nil, nil +}