diff --git a/cmd/prysmctl/validator/BUILD.bazel b/cmd/prysmctl/validator/BUILD.bazel index 639ebc00f485..bfa64df34e07 100644 --- a/cmd/prysmctl/validator/BUILD.bazel +++ b/cmd/prysmctl/validator/BUILD.bazel @@ -16,6 +16,8 @@ go_library( "//cmd/validator/flags:go_default_library", "//config/features:go_default_library", "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//encoding/bytesutil:go_default_library", "//runtime/tos:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_logrusorgru_aurora//:go_default_library", @@ -33,8 +35,10 @@ go_test( embed = [":go_default_library"], deps = [ "//beacon-chain/rpc/apimiddleware:go_default_library", + "//config/params:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", ], diff --git a/cmd/prysmctl/validator/withdraw.go b/cmd/prysmctl/validator/withdraw.go index d62c7879faf9..2b8cdcdc0903 100644 --- a/cmd/prysmctl/validator/withdraw.go +++ b/cmd/prysmctl/validator/withdraw.go @@ -15,6 +15,8 @@ import ( "github.com/prysmaticlabs/prysm/v3/api/client/beacon" "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" + "github.com/prysmaticlabs/prysm/v3/config/params" + "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" log "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" "go.opencensus.io/trace" @@ -87,6 +89,13 @@ func callWithdrawalEndpoints(ctx context.Context, host string, request []*apimid if err != nil { return err } + fork, err := client.GetFork(ctx, "head") + if err != nil { + return errors.Wrap(err, "could not retrieve current fork information") + } + if !(params.BeaconConfig().ForkVersionSchedule[bytesutil.ToBytes4(fork.CurrentVersion)] >= params.BeaconConfig().CapellaForkEpoch) { + return errors.New("setting withdrawals using the BLStoExecutionChange endpoint is only available after the Capella/Shanghai hard fork.") + } err = client.SubmitChangeBLStoExecution(ctx, request) if err != nil && strings.Contains(err.Error(), "POST error") { // just log the error, so we can check the pool for partial inclusions. diff --git a/cmd/prysmctl/validator/withdraw_test.go b/cmd/prysmctl/validator/withdraw_test.go index e99621fc3bfa..6f69a637f80b 100644 --- a/cmd/prysmctl/validator/withdraw_test.go +++ b/cmd/prysmctl/validator/withdraw_test.go @@ -3,6 +3,7 @@ package validator import ( "encoding/json" "flag" + "fmt" "net" "net/http" "net/http/httptest" @@ -10,33 +11,54 @@ import ( "path/filepath" "testing" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" logtest "github.com/sirupsen/logrus/hooks/test" "github.com/urfave/cli/v2" ) -func TestCallWithdrawalEndpoint(t *testing.T) { - file := "./testdata/change-operations.json" - baseurl := "127.0.0.1:3500" - l, err := net.Listen("tcp", baseurl) - require.NoError(t, err) - srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +func getHappyPathTestServer(file string, t *testing.T) *httptest.Server { + return httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "application/json") if r.Method == http.MethodGet { - b, err := os.ReadFile(filepath.Clean(file)) - require.NoError(t, err) - var to []*apimiddleware.SignedBLSToExecutionChangeJson - err = json.Unmarshal(b, &to) - require.NoError(t, err) - err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{ - Data: to, - }) - require.NoError(t, err) + fmt.Println(r.RequestURI) + if r.RequestURI == "/eth/v1/beacon/pool/bls_to_execution_changes" { + b, err := os.ReadFile(filepath.Clean(file)) + require.NoError(t, err) + var to []*apimiddleware.SignedBLSToExecutionChangeJson + err = json.Unmarshal(b, &to) + require.NoError(t, err) + err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{ + Data: to, + }) + require.NoError(t, err) + } else if r.RequestURI == "/eth/v1/beacon/states/head/fork" { + err := json.NewEncoder(w).Encode(&apimiddleware.StateForkResponseJson{ + Data: &apimiddleware.ForkJson{ + PreviousVersion: hexutil.Encode(params.BeaconConfig().CapellaForkVersion), + CurrentVersion: hexutil.Encode(params.BeaconConfig().CapellaForkVersion), + Epoch: fmt.Sprintf("%d", params.BeaconConfig().CapellaForkEpoch), + }, + ExecutionOptimistic: false, + Finalized: true, + }) + require.NoError(t, err) + } + } })) +} + +func TestCallWithdrawalEndpoint(t *testing.T) { + file := "./testdata/change-operations.json" + baseurl := "127.0.0.1:3500" + l, err := net.Listen("tcp", baseurl) + require.NoError(t, err) + srv := getHappyPathTestServer(file, t) err = srv.Listener.Close() require.NoError(t, err) srv.Listener = l @@ -65,21 +87,7 @@ func TestCallWithdrawalEndpoint_Mutiple(t *testing.T) { baseurl := "127.0.0.1:3500" l, err := net.Listen("tcp", baseurl) require.NoError(t, err) - srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - w.Header().Set("Content-Type", "application/json") - if r.Method == http.MethodGet { - b, err := os.ReadFile(filepath.Clean(file)) - require.NoError(t, err) - var to []*apimiddleware.SignedBLSToExecutionChangeJson - err = json.Unmarshal(b, &to) - require.NoError(t, err) - err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{ - Data: to, - }) - require.NoError(t, err) - } - })) + srv := getHappyPathTestServer(file, t) err = srv.Listener.Close() require.NoError(t, err) srv.Listener = l @@ -111,21 +119,7 @@ func TestCallWithdrawalEndpoint_Mutiple_stakingcli(t *testing.T) { baseurl := "127.0.0.1:3500" l, err := net.Listen("tcp", baseurl) require.NoError(t, err) - srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - w.Header().Set("Content-Type", "application/json") - if r.Method == http.MethodGet { - b, err := os.ReadFile(filepath.Clean(file)) - require.NoError(t, err) - var to []*apimiddleware.SignedBLSToExecutionChangeJson - err = json.Unmarshal(b, &to) - require.NoError(t, err) - err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{ - Data: to, - }) - require.NoError(t, err) - } - })) + srv := getHappyPathTestServer(file, t) err = srv.Listener.Close() require.NoError(t, err) srv.Listener = l @@ -157,21 +151,7 @@ func TestCallWithdrawalEndpoint_Mutiple_notfound(t *testing.T) { baseurl := "127.0.0.1:3500" l, err := net.Listen("tcp", baseurl) require.NoError(t, err) - srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - w.Header().Set("Content-Type", "application/json") - if r.Method == http.MethodGet { - b, err := os.ReadFile(filepath.Clean(respFile)) - require.NoError(t, err) - var to []*apimiddleware.SignedBLSToExecutionChangeJson - err = json.Unmarshal(b, &to) - require.NoError(t, err) - err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{ - Data: to, - }) - require.NoError(t, err) - } - })) + srv := getHappyPathTestServer(respFile, t) err = srv.Listener.Close() require.NoError(t, err) srv.Listener = l @@ -228,14 +208,34 @@ func TestCallWithdrawalEndpoint_Errors(t *testing.T) { l, err := net.Listen("tcp", baseurl) require.NoError(t, err) srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(400) - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(&apimiddleware.IndexedVerificationFailureErrorJson{ - Failures: []*apimiddleware.SingleIndexedVerificationFailureJson{ - {Index: 0, Message: "Could not validate SignedBLSToExecutionChange"}, - }, - }) - require.NoError(t, err) + if r.Method == http.MethodPost && r.RequestURI == "/eth/v1/beacon/pool/bls_to_execution_changes" { + w.WriteHeader(400) + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(&apimiddleware.IndexedVerificationFailureErrorJson{ + Failures: []*apimiddleware.SingleIndexedVerificationFailureJson{ + {Index: 0, Message: "Could not validate SignedBLSToExecutionChange"}, + }, + }) + require.NoError(t, err) + } else if r.Method == http.MethodGet { + if r.RequestURI == "/eth/v1/beacon/states/head/fork" { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(&apimiddleware.StateForkResponseJson{ + Data: &apimiddleware.ForkJson{ + PreviousVersion: hexutil.Encode(params.BeaconConfig().CapellaForkVersion), + CurrentVersion: hexutil.Encode(params.BeaconConfig().CapellaForkVersion), + Epoch: fmt.Sprintf("%d", params.BeaconConfig().CapellaForkEpoch), + }, + ExecutionOptimistic: false, + Finalized: true, + }) + require.NoError(t, err) + } else { + w.WriteHeader(400) + w.Header().Set("Content-Type", "application/json") + } + } })) err = srv.Listener.Close() require.NoError(t, err) @@ -260,6 +260,45 @@ func TestCallWithdrawalEndpoint_Errors(t *testing.T) { assert.LogsContain(t, hook, "Could not validate SignedBLSToExecutionChange") } +func TestCallWithdrawalEndpoint_ForkBeforeCapella(t *testing.T) { + file := "./testdata/change-operations.json" + baseurl := "127.0.0.1:3500" + l, err := net.Listen("tcp", baseurl) + require.NoError(t, err) + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(&apimiddleware.StateForkResponseJson{ + Data: &apimiddleware.ForkJson{ + PreviousVersion: hexutil.Encode(params.BeaconConfig().BellatrixForkVersion), + CurrentVersion: hexutil.Encode(params.BeaconConfig().BellatrixForkVersion), + Epoch: fmt.Sprintf("%d", params.BeaconConfig().BellatrixForkEpoch), + }, + ExecutionOptimistic: false, + Finalized: true, + }) + require.NoError(t, err) + })) + err = srv.Listener.Close() + require.NoError(t, err) + srv.Listener = l + srv.Start() + defer srv.Close() + + app := cli.App{} + set := flag.NewFlagSet("test", 0) + set.String("beacon-node-host", baseurl, "") + set.String("path", file, "") + set.Bool("confirm", true, "") + set.Bool("accept-terms-of-use", true, "") + assert.NoError(t, set.Set("beacon-node-host", baseurl)) + assert.NoError(t, set.Set("path", file)) + cliCtx := cli.NewContext(&app, set, nil) + + err = setWithdrawalAddresses(cliCtx) + require.ErrorContains(t, "setting withdrawals using the BLStoExecutionChange endpoint is only available after the Capella/Shanghai hard fork.", err) +} + func TestVerifyWithdrawal_Mutiple(t *testing.T) { file := "./testdata/change-operations-multiple.json" baseurl := "127.0.0.1:3500"