mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Keymanager API: Add validator voluntary exit endpoint (#12299)
* Initial setup * Fix + Cleanup * Add query * Fix * Add epoch * James' review part 1 * James' review part 2 * James' review part 3 * Radek' review * Gazelle * Fix cycle * Start unit test * fixing part of the test * Mostly fix test * Fix tests * Cleanup * Handle error * Remove times * Fix all tests * Fix accidental deletion * Unmarshal epoch * Add custom_type * Small fix * Fix epoch * Lint fix * Add test + fix empty query panic * Add comment * Fix regex * Add correct error message * Change current epoch to use slot * Return error if incorrect epoch passed * Remove redundant type conversion * Fix tests * gaz * Remove nodeClient + pass slot * Remove slot from parameters * Fix tests * Fix test attempt 2 * Fix test attempt 2 * Remove nodeClient from ProposeExit * Fix * Fix tests --------- Co-authored-by: james-prysm <james@prysmaticlabs.com> Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
This commit is contained in:
@@ -134,7 +134,6 @@ func TestExitAccountsCli_OK_AllPublicKeys(t *testing.T) {
|
||||
|
||||
mockNodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Times(2).
|
||||
Return(ðpb.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
mockValidatorClient.EXPECT().
|
||||
|
||||
1057
proto/eth/service/key_management.pb.go
generated
1057
proto/eth/service/key_management.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -597,6 +597,76 @@ func local_request_KeyManagement_DeleteGasLimit_0(ctx context.Context, marshaler
|
||||
|
||||
}
|
||||
|
||||
func request_KeyManagement_SetVoluntaryExit_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq SetVoluntaryExitRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["pubkey"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
|
||||
}
|
||||
|
||||
pubkey, err := runtime.Bytes(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
|
||||
}
|
||||
protoReq.Pubkey = (pubkey)
|
||||
|
||||
msg, err := client.SetVoluntaryExit(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_KeyManagement_SetVoluntaryExit_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq SetVoluntaryExitRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["pubkey"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
|
||||
}
|
||||
|
||||
pubkey, err := runtime.Bytes(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
|
||||
}
|
||||
protoReq.Pubkey = (pubkey)
|
||||
|
||||
msg, err := server.SetVoluntaryExit(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterKeyManagementHandlerServer registers the http handlers for service KeyManagement to "mux".
|
||||
// UnaryRPC :call KeyManagementServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
@@ -879,6 +949,29 @@ func RegisterKeyManagementHandlerServer(ctx context.Context, mux *runtime.ServeM
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_KeyManagement_SetVoluntaryExit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/ethereum.eth.service.KeyManagement/SetVoluntaryExit")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_KeyManagement_SetVoluntaryExit_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_KeyManagement_SetVoluntaryExit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1160,6 +1253,26 @@ func RegisterKeyManagementHandlerClient(ctx context.Context, mux *runtime.ServeM
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_KeyManagement_SetVoluntaryExit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/ethereum.eth.service.KeyManagement/SetVoluntaryExit")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_KeyManagement_SetVoluntaryExit_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_KeyManagement_SetVoluntaryExit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1187,6 +1300,8 @@ var (
|
||||
pattern_KeyManagement_SetGasLimit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "gas_limit"}, ""))
|
||||
|
||||
pattern_KeyManagement_DeleteGasLimit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "gas_limit"}, ""))
|
||||
|
||||
pattern_KeyManagement_SetVoluntaryExit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "voluntary_exit"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -1213,4 +1328,6 @@ var (
|
||||
forward_KeyManagement_SetGasLimit_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_KeyManagement_DeleteGasLimit_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_KeyManagement_SetVoluntaryExit_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ package ethereum.eth.service;
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "proto/eth/ext/options.proto";
|
||||
|
||||
option csharp_namespace = "Ethereum.Eth.Service";
|
||||
option go_package = "github.com/prysmaticlabs/prysm/v4/proto/eth/service";
|
||||
@@ -217,6 +218,24 @@ service KeyManagement {
|
||||
};
|
||||
}
|
||||
|
||||
// SetVoluntaryExit creates a signed voluntary exit message and returns a VoluntaryExit object.
|
||||
//
|
||||
// Spec page: https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Voluntary%20Exit/signVoluntaryExit
|
||||
//
|
||||
// HTTP response status codes:
|
||||
// - 200: Successfully created and signed voluntary exit
|
||||
// - 400: Bad request, malformed request
|
||||
// - 401: Unauthorized, no token is found.
|
||||
// - 403: Forbidden, a token is found but is invalid
|
||||
// - 404: Path not found
|
||||
// - 500: Validator internal error
|
||||
rpc SetVoluntaryExit(SetVoluntaryExitRequest) returns (SetVoluntaryExitResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/internal/eth/v1/validator/{pubkey}/voluntary_exit",
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
message ListKeystoresResponse {
|
||||
@@ -352,3 +371,20 @@ message SetGasLimitRequest {
|
||||
message DeleteGasLimitRequest {
|
||||
bytes pubkey = 1;
|
||||
}
|
||||
|
||||
message SetVoluntaryExitRequest {
|
||||
bytes pubkey = 1 [(ethereum.eth.ext.ssz_size) = "48"];
|
||||
uint64 epoch = 2 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives.Epoch"];
|
||||
}
|
||||
|
||||
message SetVoluntaryExitResponse {
|
||||
message SignedVoluntaryExit {
|
||||
message VoluntaryExit {
|
||||
uint64 epoch = 1 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives.Epoch"];
|
||||
uint64 validator_index = 2 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives.ValidatorIndex"];
|
||||
}
|
||||
VoluntaryExit message = 1;
|
||||
bytes signature = 2;
|
||||
}
|
||||
SignedVoluntaryExit data = 1;
|
||||
}
|
||||
|
||||
@@ -84,11 +84,19 @@ func PerformVoluntaryExit(
|
||||
ctx context.Context, cfg PerformExitCfg,
|
||||
) (rawExitedKeys [][]byte, formattedExitedKeys []string, err error) {
|
||||
var rawNotExitedKeys [][]byte
|
||||
genesisResponse, err := cfg.NodeClient.GetGenesis(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("voluntary exit failed: %v", err)
|
||||
}
|
||||
for i, key := range cfg.RawPubKeys {
|
||||
// When output directory is present, only create the signed exit, but do not propose it.
|
||||
// Otherwise, propose the exit immediately.
|
||||
epoch, err := client.CurrentEpoch(genesisResponse.GenesisTime)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("voluntary exit failed: %v", err)
|
||||
}
|
||||
if len(cfg.OutputDirectory) > 0 {
|
||||
sve, err := client.CreateSignedVoluntaryExit(ctx, cfg.ValidatorClient, cfg.NodeClient, cfg.Keymanager.Sign, key)
|
||||
sve, err := client.CreateSignedVoluntaryExit(ctx, cfg.ValidatorClient, cfg.Keymanager.Sign, key, epoch)
|
||||
if err != nil {
|
||||
rawNotExitedKeys = append(rawNotExitedKeys, key)
|
||||
msg := err.Error()
|
||||
@@ -101,7 +109,7 @@ func PerformVoluntaryExit(
|
||||
} else if err := writeSignedVoluntaryExitJSON(ctx, sve, cfg.OutputDirectory); err != nil {
|
||||
log.WithError(err).Error("failed to write voluntary exit")
|
||||
}
|
||||
} else if err := client.ProposeExit(ctx, cfg.ValidatorClient, cfg.NodeClient, cfg.Keymanager.Sign, key); err != nil {
|
||||
} else if err := client.ProposeExit(ctx, cfg.ValidatorClient, cfg.Keymanager.Sign, key, epoch); err != nil {
|
||||
rawNotExitedKeys = append(rawNotExitedKeys, key)
|
||||
|
||||
msg := err.Error()
|
||||
|
||||
@@ -80,6 +80,7 @@ go_library(
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
|
||||
"@io_opencensus_go//plugin/ocgrpc:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
@@ -173,6 +174,5 @@ go_test(
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/async"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
@@ -25,7 +26,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
const domainDataErr = "could not get domain data"
|
||||
@@ -198,14 +198,14 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
|
||||
func ProposeExit(
|
||||
ctx context.Context,
|
||||
validatorClient iface.ValidatorClient,
|
||||
nodeClient iface.NodeClient,
|
||||
signer iface.SigningFunc,
|
||||
pubKey []byte,
|
||||
epoch primitives.Epoch,
|
||||
) error {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.ProposeExit")
|
||||
defer span.End()
|
||||
|
||||
signedExit, err := CreateSignedVoluntaryExit(ctx, validatorClient, nodeClient, signer, pubKey)
|
||||
signedExit, err := CreateSignedVoluntaryExit(ctx, validatorClient, signer, pubKey, epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create signed voluntary exit")
|
||||
}
|
||||
@@ -217,16 +217,22 @@ func ProposeExit(
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("exitRoot", fmt.Sprintf("%#x", exitResp.ExitRoot)),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CurrentEpoch(genesisTime *timestamp.Timestamp) (primitives.Epoch, error) {
|
||||
totalSecondsPassed := prysmTime.Now().Unix() - genesisTime.Seconds
|
||||
currentSlot := primitives.Slot((uint64(totalSecondsPassed)) / params.BeaconConfig().SecondsPerSlot)
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
return currentEpoch, nil
|
||||
}
|
||||
|
||||
func CreateSignedVoluntaryExit(
|
||||
ctx context.Context,
|
||||
validatorClient iface.ValidatorClient,
|
||||
nodeClient iface.NodeClient,
|
||||
signer iface.SigningFunc,
|
||||
pubKey []byte,
|
||||
epoch primitives.Epoch,
|
||||
) (*ethpb.SignedVoluntaryExit, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.CreateSignedVoluntaryExit")
|
||||
defer span.End()
|
||||
@@ -235,15 +241,12 @@ func CreateSignedVoluntaryExit(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gRPC call to get validator index failed")
|
||||
}
|
||||
genesisResponse, err := nodeClient.GetGenesis(ctx, &emptypb.Empty{})
|
||||
exit := ðpb.VoluntaryExit{Epoch: epoch, ValidatorIndex: indexResponse.Index}
|
||||
slot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gRPC call to get genesis time failed")
|
||||
return nil, errors.Wrap(err, "failed to retrieve slot")
|
||||
}
|
||||
totalSecondsPassed := prysmTime.Now().Unix() - genesisResponse.GenesisTime.Seconds
|
||||
currentEpoch := primitives.Epoch(uint64(totalSecondsPassed) / uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)))
|
||||
currentSlot := slots.CurrentSlot(uint64(genesisResponse.GenesisTime.AsTime().Unix()))
|
||||
exit := ðpb.VoluntaryExit{Epoch: currentEpoch, ValidatorIndex: indexResponse.Index}
|
||||
sig, err := signVoluntaryExit(ctx, validatorClient, signer, pubKey, exit, currentSlot)
|
||||
sig, err := signVoluntaryExit(ctx, validatorClient, signer, pubKey, exit, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign voluntary exit")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
testing2 "github.com/prysmaticlabs/prysm/v4/validator/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/graffiti"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type mocks struct {
|
||||
@@ -648,39 +646,15 @@ func TestProposeExit_ValidatorIndexFailed(t *testing.T) {
|
||||
err := ProposeExit(
|
||||
context.Background(),
|
||||
m.validatorClient,
|
||||
m.nodeClient,
|
||||
m.signfunc,
|
||||
validatorKey.PublicKey().Marshal(),
|
||||
params.BeaconConfig().GenesisEpoch,
|
||||
)
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorContains(t, "uh oh", err)
|
||||
assert.ErrorContains(t, "gRPC call to get validator index failed", err)
|
||||
}
|
||||
|
||||
func TestProposeExit_GetGenesisFailed(t *testing.T) {
|
||||
_, m, validatorKey, finish := setup(t)
|
||||
defer finish()
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
ValidatorIndex(gomock.Any(), gomock.Any()).
|
||||
Return(nil, nil)
|
||||
|
||||
m.nodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("uh oh"))
|
||||
|
||||
err := ProposeExit(
|
||||
context.Background(),
|
||||
m.validatorClient,
|
||||
m.nodeClient,
|
||||
m.signfunc,
|
||||
validatorKey.PublicKey().Marshal(),
|
||||
)
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorContains(t, "uh oh", err)
|
||||
assert.ErrorContains(t, "gRPC call to get genesis time failed", err)
|
||||
}
|
||||
|
||||
func TestProposeExit_DomainDataFailed(t *testing.T) {
|
||||
_, m, validatorKey, finish := setup(t)
|
||||
defer finish()
|
||||
@@ -689,15 +663,6 @@ func TestProposeExit_DomainDataFailed(t *testing.T) {
|
||||
ValidatorIndex(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.ValidatorIndexResponse{Index: 1}, nil)
|
||||
|
||||
// Any time in the past will suffice
|
||||
genesisTime := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
m.nodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("uh oh"))
|
||||
@@ -705,9 +670,9 @@ func TestProposeExit_DomainDataFailed(t *testing.T) {
|
||||
err := ProposeExit(
|
||||
context.Background(),
|
||||
m.validatorClient,
|
||||
m.nodeClient,
|
||||
m.signfunc,
|
||||
validatorKey.PublicKey().Marshal(),
|
||||
params.BeaconConfig().GenesisEpoch,
|
||||
)
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorContains(t, domainDataErr, err)
|
||||
@@ -723,15 +688,6 @@ func TestProposeExit_DomainDataIsNil(t *testing.T) {
|
||||
ValidatorIndex(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.ValidatorIndexResponse{Index: 1}, nil)
|
||||
|
||||
// Any time in the past will suffice
|
||||
genesisTime := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
m.nodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(nil, nil)
|
||||
@@ -739,9 +695,9 @@ func TestProposeExit_DomainDataIsNil(t *testing.T) {
|
||||
err := ProposeExit(
|
||||
context.Background(),
|
||||
m.validatorClient,
|
||||
m.nodeClient,
|
||||
m.signfunc,
|
||||
validatorKey.PublicKey().Marshal(),
|
||||
params.BeaconConfig().GenesisEpoch,
|
||||
)
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorContains(t, domainDataErr, err)
|
||||
@@ -756,15 +712,6 @@ func TestProposeBlock_ProposeExitFailed(t *testing.T) {
|
||||
ValidatorIndex(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.ValidatorIndexResponse{Index: 1}, nil)
|
||||
|
||||
// Any time in the past will suffice
|
||||
genesisTime := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
m.nodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil)
|
||||
@@ -776,9 +723,9 @@ func TestProposeBlock_ProposeExitFailed(t *testing.T) {
|
||||
err := ProposeExit(
|
||||
context.Background(),
|
||||
m.validatorClient,
|
||||
m.nodeClient,
|
||||
m.signfunc,
|
||||
validatorKey.PublicKey().Marshal(),
|
||||
params.BeaconConfig().GenesisEpoch,
|
||||
)
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorContains(t, "uh oh", err)
|
||||
@@ -793,15 +740,6 @@ func TestProposeExit_BroadcastsBlock(t *testing.T) {
|
||||
ValidatorIndex(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.ValidatorIndexResponse{Index: 1}, nil)
|
||||
|
||||
// Any time in the past will suffice
|
||||
genesisTime := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
m.nodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
m.validatorClient.EXPECT().
|
||||
DomainData(gomock.Any(), gomock.Any()).
|
||||
Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil)
|
||||
@@ -813,9 +751,9 @@ func TestProposeExit_BroadcastsBlock(t *testing.T) {
|
||||
assert.NoError(t, ProposeExit(
|
||||
context.Background(),
|
||||
m.validatorClient,
|
||||
m.nodeClient,
|
||||
m.signfunc,
|
||||
validatorKey.PublicKey().Marshal(),
|
||||
params.BeaconConfig().GenesisEpoch,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ go_test(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//config/validator/service:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
@@ -138,6 +139,7 @@ go_test(
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -205,7 +205,6 @@ func TestServer_VoluntaryExit(t *testing.T) {
|
||||
|
||||
mockNodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Times(2).
|
||||
Return(ðpb.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
mockValidatorClient.EXPECT().
|
||||
|
||||
@@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"custom_hooks.go",
|
||||
"endpoint_factory.go",
|
||||
"structs.go",
|
||||
],
|
||||
@@ -16,11 +17,17 @@ go_library(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["structs_test.go"],
|
||||
srcs = [
|
||||
"custom_hooks_test.go",
|
||||
"structs_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/gateway/apimiddleware:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
39
validator/rpc/apimiddleware/custom_hooks.go
Normal file
39
validator/rpc/apimiddleware/custom_hooks.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package apimiddleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
|
||||
)
|
||||
|
||||
// "/eth/v1/validator/{pubkey}/voluntary_exit" POST expects epoch as a query param.
|
||||
// This hook adds the query param to the body so that it is a valid POST request as
|
||||
// grpc-gateway does not handle query params in POST requests.
|
||||
func setVoluntaryExitEpoch(
|
||||
endpoint *apimiddleware.Endpoint,
|
||||
_ http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) (apimiddleware.RunDefault, apimiddleware.ErrorJson) {
|
||||
if _, ok := endpoint.PostRequest.(*SetVoluntaryExitRequestJson); ok {
|
||||
var epoch = req.URL.Query().Get("epoch")
|
||||
// To handle the request without the query param
|
||||
if epoch == "" {
|
||||
epoch = "0"
|
||||
}
|
||||
_, err := strconv.ParseUint(epoch, 10, 64)
|
||||
if err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "invalid epoch")
|
||||
}
|
||||
j := &SetVoluntaryExitRequestJson{Epoch: epoch}
|
||||
b, err := json.Marshal(j)
|
||||
if err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not marshal epoch")
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(b))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
49
validator/rpc/apimiddleware/custom_hooks_test.go
Normal file
49
validator/rpc/apimiddleware/custom_hooks_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package apimiddleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
func TestSetVoluntaryExitEpoch(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
PostRequest: &SetVoluntaryExitRequestJson{},
|
||||
}
|
||||
epoch := "300"
|
||||
|
||||
var body bytes.Buffer
|
||||
request := httptest.NewRequest("POST", fmt.Sprintf("http://foo.example?epoch=%s", epoch), &body)
|
||||
|
||||
runDefault, errJson := setVoluntaryExitEpoch(endpoint, nil, request)
|
||||
require.Equal(t, true, errJson == nil)
|
||||
assert.Equal(t, apimiddleware.RunDefault(true), runDefault)
|
||||
|
||||
var b SetVoluntaryExitRequestJson
|
||||
err := json.NewDecoder(request.Body).Decode(&b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, epoch, b.Epoch)
|
||||
})
|
||||
t.Run("invalid query returns error", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
PostRequest: &SetVoluntaryExitRequestJson{},
|
||||
}
|
||||
epoch := "/12"
|
||||
var body bytes.Buffer
|
||||
request := httptest.NewRequest("POST", fmt.Sprintf("http://foo.example?epoch=%s", epoch), &body)
|
||||
|
||||
runDefault, errJson := setVoluntaryExitEpoch(endpoint, nil, request)
|
||||
assert.NotNil(t, errJson)
|
||||
assert.Equal(t, apimiddleware.RunDefault(false), runDefault)
|
||||
err := errors.New(errJson.Msg())
|
||||
assert.ErrorContains(t, "invalid epoch", err)
|
||||
})
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func (*ValidatorEndpointFactory) Paths() []string {
|
||||
"/eth/v1/remotekeys",
|
||||
"/eth/v1/validator/{pubkey}/feerecipient",
|
||||
"/eth/v1/validator/{pubkey}/gas_limit",
|
||||
"/eth/v1/validator/{pubkey}/voluntary_exit",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +48,12 @@ func (*ValidatorEndpointFactory) Create(path string) (*apimiddleware.Endpoint, e
|
||||
endpoint.GetResponse = &GetGasLimitResponseJson{}
|
||||
endpoint.PostRequest = &SetGasLimitRequestJson{}
|
||||
endpoint.DeleteRequest = &DeleteGasLimitRequestJson{}
|
||||
case "/eth/v1/validator/{pubkey}/voluntary_exit":
|
||||
endpoint.PostRequest = &SetVoluntaryExitRequestJson{}
|
||||
endpoint.PostResponse = &SetVoluntaryExitResponseJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: setVoluntaryExitEpoch,
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("invalid path")
|
||||
}
|
||||
|
||||
@@ -100,3 +100,22 @@ type SetGasLimitRequestJson struct {
|
||||
type DeleteGasLimitRequestJson struct {
|
||||
Pubkey string `json:"pubkey" hex:"true"`
|
||||
}
|
||||
|
||||
type SetVoluntaryExitRequestJson struct {
|
||||
Pubkey string `json:"pubkey" hex:"true"`
|
||||
Epoch string `json:"epoch"`
|
||||
}
|
||||
|
||||
type SetVoluntaryExitResponseJson struct {
|
||||
SignedVoluntaryExit *SignedVoluntaryExitJson `json:"data"`
|
||||
}
|
||||
|
||||
type SignedVoluntaryExitJson struct {
|
||||
VoluntaryExit *VoluntaryExitJson `json:"message"`
|
||||
Signature string `json:"signature" hex:"true"`
|
||||
}
|
||||
|
||||
type VoluntaryExitJson struct {
|
||||
Epoch string `json:"epoch"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
||||
slashingprotection "github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// ListKeystores implements the standard validator key management API.
|
||||
@@ -679,3 +681,51 @@ func validatePublicKey(pubkey []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetVoluntaryExit creates a signed voluntary exit message and returns a VoluntaryExit object.
|
||||
func (s *Server) SetVoluntaryExit(ctx context.Context, req *ethpbservice.SetVoluntaryExitRequest) (*ethpbservice.SetVoluntaryExitResponse, error) {
|
||||
if s.validatorService == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready")
|
||||
}
|
||||
if err := validatePublicKey(req.Pubkey); err != nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, err.Error())
|
||||
}
|
||||
if s.wallet == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Epoch == 0 {
|
||||
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not create voluntary exit: %v", err)
|
||||
}
|
||||
epoch, err := client.CurrentEpoch(genesisResponse.GenesisTime)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "gRPC call to get genesis time failed: %v", err)
|
||||
}
|
||||
req.Epoch = epoch
|
||||
}
|
||||
sve, err := client.CreateSignedVoluntaryExit(
|
||||
ctx,
|
||||
s.beaconNodeValidatorClient,
|
||||
km.Sign,
|
||||
req.Pubkey,
|
||||
req.Epoch,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not create voluntary exit: %v", err)
|
||||
}
|
||||
|
||||
return ðpbservice.SetVoluntaryExitResponse{
|
||||
Data: ðpbservice.SetVoluntaryExitResponse_SignedVoluntaryExit{
|
||||
Message: ðpbservice.SetVoluntaryExitResponse_SignedVoluntaryExit_VoluntaryExit{
|
||||
Epoch: uint64(sve.Exit.Epoch),
|
||||
ValidatorIndex: uint64(sve.Exit.ValidatorIndex),
|
||||
},
|
||||
Signature: sve.Signature,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -17,6 +19,7 @@ import (
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
validatorserviceconfig "github.com/prysmaticlabs/prysm/v4/config/validator/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
@@ -41,6 +44,8 @@ import (
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestServer_ListKeystores(t *testing.T) {
|
||||
@@ -1540,3 +1545,116 @@ func TestServer_DeleteGasLimit(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetVoluntaryExit(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
|
||||
defaultWalletPath = setupWalletDir(t)
|
||||
opts := []accounts.Option{
|
||||
accounts.WithWalletDir(defaultWalletPath),
|
||||
accounts.WithKeymanagerType(keymanager.Derived),
|
||||
accounts.WithWalletPassword(strongPass),
|
||||
accounts.WithSkipMnemonicConfirm(true),
|
||||
}
|
||||
acc, err := accounts.NewCLIManager(opts...)
|
||||
require.NoError(t, err)
|
||||
w, err := acc.WalletCreate(ctx)
|
||||
require.NoError(t, err)
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
|
||||
require.NoError(t, err)
|
||||
|
||||
m := &mock.MockValidator{Km: km}
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Validator: m,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1)
|
||||
require.NoError(t, err)
|
||||
pubKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
beaconClient := validatormock.NewMockValidatorClient(ctrl)
|
||||
mockNodeClient := validatormock.NewMockNodeClient(ctrl)
|
||||
// Any time in the past will suffice
|
||||
genesisTime := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
beaconClient.EXPECT().ValidatorIndex(gomock.Any(), ð.ValidatorIndexRequest{PublicKey: pubKeys[0][:]}).
|
||||
Times(3).
|
||||
Return(ð.ValidatorIndexResponse{Index: 2}, nil)
|
||||
|
||||
beaconClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(), // epoch
|
||||
).Times(3).
|
||||
Return(ð.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
|
||||
|
||||
mockNodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Times(3).
|
||||
Return(ð.Genesis{GenesisTime: genesisTime}, nil)
|
||||
|
||||
s := &Server{
|
||||
validatorService: vs,
|
||||
beaconNodeValidatorClient: beaconClient,
|
||||
wallet: w,
|
||||
beaconNodeClient: mockNodeClient,
|
||||
}
|
||||
|
||||
type want struct {
|
||||
epoch primitives.Epoch
|
||||
validatorIndex uint64
|
||||
signature []byte
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pubkey []byte
|
||||
epoch primitives.Epoch
|
||||
w want
|
||||
}{
|
||||
{
|
||||
name: "Ok: with epoch",
|
||||
epoch: 30000000,
|
||||
w: want{
|
||||
epoch: 30000000,
|
||||
validatorIndex: 2,
|
||||
signature: []uint8{175, 157, 5, 134, 253, 2, 193, 35, 176, 43, 217, 36, 39, 240, 24, 79, 207, 133, 150, 7, 237, 16, 54, 244, 64, 27, 244, 17, 8, 225, 140, 1, 172, 24, 35, 95, 178, 116, 172, 213, 113, 182, 193, 61, 192, 65, 162, 253, 19, 202, 111, 164, 195, 215, 0, 205, 95, 7, 30, 251, 244, 157, 210, 155, 238, 30, 35, 219, 177, 232, 174, 62, 218, 69, 23, 249, 180, 140, 60, 29, 190, 249, 229, 95, 235, 236, 81, 33, 60, 4, 201, 227, 70, 239, 167, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ok: epoch not set",
|
||||
w: want{
|
||||
epoch: 0,
|
||||
validatorIndex: 2,
|
||||
signature: []uint8{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resp, err := s.SetVoluntaryExit(ctx, ðpbservice.SetVoluntaryExitRequest{Pubkey: pubKeys[0][:], Epoch: tt.epoch})
|
||||
require.NoError(t, err)
|
||||
if tt.w.epoch == 0 {
|
||||
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
tt.w.epoch, err = client.CurrentEpoch(genesisResponse.GenesisTime)
|
||||
require.NoError(t, err)
|
||||
resp2, err := s.SetVoluntaryExit(ctx, ðpbservice.SetVoluntaryExitRequest{Pubkey: pubKeys[0][:], Epoch: tt.epoch})
|
||||
require.NoError(t, err)
|
||||
tt.w.signature = resp2.Data.Signature
|
||||
}
|
||||
require.Equal(t, uint64(tt.w.epoch), resp.Data.Message.Epoch)
|
||||
require.Equal(t, tt.w.validatorIndex, resp.Data.Message.ValidatorIndex)
|
||||
require.NotEmpty(t, resp.Data.Signature)
|
||||
ok = bytes.Equal(tt.w.signature, resp.Data.Signature)
|
||||
require.Equal(t, true, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user