-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add REST implementation for Validator's WaitForChainStart (#11654)
* Add REST implementation for Validator's WaitForChainStart * Add missing error mapping * Add missing bazel dependency * Add missing tests * Address PR comments * Replace EventErrorJson with DefaultErrorJson * Add tests for WaitForChainStart * Refactor tests * Address PR comments * Add gazelle:build_tag use_beacon_api comment in BUILD.bazel * Address PR comments * Address PR comments Co-authored-by: Radosław Kapka <rkapka@wp.pl>
- Loading branch information
1 parent
fe1281d
commit 0d4b98c
Showing
15 changed files
with
690 additions
and
203 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,43 @@ | ||
load("@prysm//tools/go:def.bzl", "go_library") | ||
load("@prysm//tools/go:def.bzl", "go_library", "go_test") | ||
|
||
# Gazelle will flag this for removal since it's only being used with the use_beacon_api flag, but it should be kept | ||
# keep | ||
# gazelle:build_tags use_beacon_api | ||
go_library( | ||
name = "go_default_library", | ||
srcs = ["beacon_api_validator_client.go"], | ||
srcs = [ | ||
"beacon_api_helpers.go", | ||
"beacon_api_validator_client.go", | ||
"genesis.go", | ||
], | ||
importpath = "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api", | ||
visibility = ["//validator:__subpackages__"], | ||
deps = [ | ||
"//api/gateway/apimiddleware:go_default_library", | ||
"//beacon-chain/rpc/apimiddleware:go_default_library", | ||
"//proto/prysm/v1alpha1:go_default_library", | ||
"//validator/client/iface:go_default_library", | ||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", | ||
"@com_github_pkg_errors//:go_default_library", | ||
"@io_bazel_rules_go//proto/wkt:empty_go_proto", | ||
], | ||
) | ||
|
||
# gazelle:build_tags use_beacon_api | ||
go_test( | ||
name = "go_default_test", | ||
size = "small", | ||
srcs = [ | ||
"beacon_api_helpers_test.go", | ||
"genesis_test.go", | ||
"handlers_test.go", | ||
"wait_for_chain_start_test.go", | ||
], | ||
embed = [":go_default_library"], | ||
deps = [ | ||
"//api/gateway/apimiddleware:go_default_library", | ||
"//beacon-chain/rpc/apimiddleware:go_default_library", | ||
"//testing/assert:go_default_library", | ||
"//testing/require:go_default_library", | ||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", | ||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
//go:build use_beacon_api | ||
// +build use_beacon_api | ||
|
||
package beacon_api | ||
|
||
import ( | ||
"regexp" | ||
) | ||
|
||
func validRoot(root string) bool { | ||
matchesRegex, err := regexp.MatchString("^0x[a-fA-F0-9]{64}$", root) | ||
if err != nil { | ||
return false | ||
} | ||
return matchesRegex | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//go:build use_beacon_api | ||
// +build use_beacon_api | ||
|
||
package beacon_api | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prysmaticlabs/prysm/v3/testing/assert" | ||
) | ||
|
||
func TestBeaconApiHelpers(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
input string | ||
valid bool | ||
}{ | ||
{ | ||
name: "correct format", | ||
input: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", | ||
valid: true, | ||
}, | ||
{ | ||
name: "root too small", | ||
input: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f", | ||
valid: false, | ||
}, | ||
{ | ||
name: "root too big", | ||
input: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f22", | ||
valid: false, | ||
}, | ||
{ | ||
name: "empty root", | ||
input: "", | ||
valid: false, | ||
}, | ||
{ | ||
name: "no 0x prefix", | ||
input: "cf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", | ||
valid: false, | ||
}, | ||
{ | ||
name: "invalid characters", | ||
input: "0xzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", | ||
valid: false, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
assert.Equal(t, tt.valid, validRoot(tt.input)) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
//go:build use_beacon_api | ||
// +build use_beacon_api | ||
|
||
package beacon_api | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"strconv" | ||
|
||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/pkg/errors" | ||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware" | ||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" | ||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" | ||
) | ||
|
||
func (c beaconApiValidatorClient) waitForChainStart() (*ethpb.ChainStartResponse, error) { | ||
genesis, err := c.getGenesis() | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to get genesis data") | ||
} | ||
|
||
genesisTime, err := strconv.ParseUint(genesis.Data.GenesisTime, 10, 64) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "failed to parse genesis time: %s", genesis.Data.GenesisTime) | ||
} | ||
|
||
chainStartResponse := ðpb.ChainStartResponse{} | ||
chainStartResponse.Started = true | ||
chainStartResponse.GenesisTime = genesisTime | ||
|
||
if !validRoot(genesis.Data.GenesisValidatorsRoot) { | ||
return nil, errors.Errorf("invalid genesis validators root: %s", genesis.Data.GenesisValidatorsRoot) | ||
} | ||
|
||
genesisValidatorRoot, err := hexutil.Decode(genesis.Data.GenesisValidatorsRoot) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to decode genesis validators root") | ||
} | ||
chainStartResponse.GenesisValidatorsRoot = genesisValidatorRoot | ||
|
||
return chainStartResponse, nil | ||
} | ||
|
||
func (c beaconApiValidatorClient) getGenesis() (*rpcmiddleware.GenesisResponseJson, error) { | ||
resp, err := c.httpClient.Get(c.url + "/eth/v1/beacon/genesis") | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to query REST API genesis endpoint") | ||
} | ||
defer func() { | ||
if err = resp.Body.Close(); err != nil { | ||
return | ||
} | ||
}() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
errorJson := apimiddleware.DefaultErrorJson{} | ||
if err := json.NewDecoder(resp.Body).Decode(&errorJson); err != nil { | ||
return nil, errors.Wrap(err, "failed to decode response body genesis error json") | ||
} | ||
|
||
return nil, errors.Errorf("error %d: %s", errorJson.Code, errorJson.Message) | ||
} | ||
|
||
genesisJson := &rpcmiddleware.GenesisResponseJson{} | ||
if err := json.NewDecoder(resp.Body).Decode(&genesisJson); err != nil { | ||
return nil, errors.Wrap(err, "failed to decode response body genesis json") | ||
} | ||
|
||
if genesisJson.Data == nil { | ||
return nil, errors.New("GenesisResponseJson.Data is nil") | ||
} | ||
|
||
return genesisJson, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
//go:build use_beacon_api | ||
// +build use_beacon_api | ||
|
||
package beacon_api | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" | ||
"github.com/prysmaticlabs/prysm/v3/testing/assert" | ||
"github.com/prysmaticlabs/prysm/v3/testing/require" | ||
) | ||
|
||
func TestGetGenesis_ValidGenesis(t *testing.T) { | ||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{ | ||
GenesisTime: "1234", | ||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", | ||
})) | ||
defer server.Close() | ||
|
||
validatorClient := &beaconApiValidatorClient{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}} | ||
resp, err := validatorClient.getGenesis() | ||
assert.NoError(t, err) | ||
require.NotNil(t, resp) | ||
require.NotNil(t, resp.Data) | ||
assert.Equal(t, "1234", resp.Data.GenesisTime) | ||
assert.Equal(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", resp.Data.GenesisValidatorsRoot) | ||
} | ||
|
||
func TestGetGenesis_NilData(t *testing.T) { | ||
server := httptest.NewServer(createGenesisHandler(nil)) | ||
defer server.Close() | ||
|
||
validatorClient := &beaconApiValidatorClient{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}} | ||
_, err := validatorClient.getGenesis() | ||
assert.ErrorContains(t, "GenesisResponseJson.Data is nil", err) | ||
} | ||
|
||
func TestGetGenesis_InvalidJsonGenesis(t *testing.T) { | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
_, err := w.Write([]byte("foo")) | ||
require.NoError(t, err) | ||
})) | ||
defer server.Close() | ||
|
||
validatorClient := &beaconApiValidatorClient{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}} | ||
_, err := validatorClient.getGenesis() | ||
assert.ErrorContains(t, "failed to decode response body genesis json", err) | ||
} | ||
|
||
func TestGetGenesis_InvalidJsonError(t *testing.T) { | ||
server := httptest.NewServer(http.HandlerFunc(invalidJsonErrHandler)) | ||
defer server.Close() | ||
|
||
validatorClient := &beaconApiValidatorClient{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}} | ||
_, err := validatorClient.getGenesis() | ||
assert.ErrorContains(t, "failed to decode response body genesis error json", err) | ||
} | ||
|
||
func TestGetGenesis_Timeout(t *testing.T) { | ||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{ | ||
GenesisTime: "1234", | ||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", | ||
})) | ||
defer server.Close() | ||
|
||
validatorClient := &beaconApiValidatorClient{url: server.URL, httpClient: http.Client{Timeout: 1}} | ||
_, err := validatorClient.getGenesis() | ||
assert.ErrorContains(t, "failed to query REST API genesis endpoint", err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//go:build use_beacon_api | ||
// +build use_beacon_api | ||
|
||
package beacon_api | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware" | ||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" | ||
) | ||
|
||
func internalServerErrHandler(w http.ResponseWriter, r *http.Request) { | ||
internalErrorJson := &apimiddleware.DefaultErrorJson{ | ||
Code: http.StatusInternalServerError, | ||
Message: "Internal server error", | ||
} | ||
|
||
marshalledError, err := json.Marshal(internalErrorJson) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
w.WriteHeader(http.StatusInternalServerError) | ||
_, err = w.Write(marshalledError) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func notFoundErrHandler(w http.ResponseWriter, r *http.Request) { | ||
internalErrorJson := &apimiddleware.DefaultErrorJson{ | ||
Code: http.StatusNotFound, | ||
Message: "Not found", | ||
} | ||
|
||
marshalledError, err := json.Marshal(internalErrorJson) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
w.WriteHeader(http.StatusNotFound) | ||
_, err = w.Write(marshalledError) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func invalidErr999Handler(w http.ResponseWriter, r *http.Request) { | ||
internalErrorJson := &apimiddleware.DefaultErrorJson{ | ||
Code: 999, | ||
Message: "Invalid error", | ||
} | ||
|
||
marshalledError, err := json.Marshal(internalErrorJson) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
w.WriteHeader(999) | ||
_, err = w.Write(marshalledError) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func invalidJsonErrHandler(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusNotFound) | ||
_, err := w.Write([]byte("foo")) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func createGenesisHandler(data *rpcmiddleware.GenesisResponse_GenesisJson) http.HandlerFunc { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
genesisResponseJson := &rpcmiddleware.GenesisResponseJson{Data: data} | ||
marshalledResponse, err := json.Marshal(genesisResponseJson) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
_, err = w.Write(marshalledResponse) | ||
if err != nil { | ||
panic(err) | ||
} | ||
}) | ||
} |
Oops, something went wrong.