diff --git a/ethereum/consensus/client/client.go b/ethereum/consensus/client/client.go index 6e3891c..e90d31a 100644 --- a/ethereum/consensus/client/client.go +++ b/ethereum/consensus/client/client.go @@ -100,6 +100,9 @@ type BeaconClient interface { // GetVoluntaryExits returns voluntary exits known by the node but not necessarily incorporated into any block. GetVoluntaryExits(ctx context.Context) (beaconphase0.VoluntaryExits, error) + + // SubmitSignedVoluntaryExit submits a signed voluntary exit to the beacon node. + SubmitSignedVoluntaryExit(ctx context.Context, epoch beaconcommon.Epoch, validatorIdx uint64, signature string) (string, error) } type NodeClient interface { diff --git a/ethereum/consensus/client/http/submit_signed_voluntary_exist.go b/ethereum/consensus/client/http/submit_signed_voluntary_exist.go new file mode 100644 index 0000000..f35aac6 --- /dev/null +++ b/ethereum/consensus/client/http/submit_signed_voluntary_exist.go @@ -0,0 +1,79 @@ +package eth2http + +import ( + "context" + "net/http" + "strconv" + + "github.com/Azure/go-autorest/autorest" + beaconcommon "github.com/protolambda/zrnt/eth2/beacon/common" +) + +type signedVoluntaryExit struct { + Message message `json:"message"` + Signature string `json:"signature"` +} + +type message struct { + Epoch beaconcommon.Epoch `json:"epoch"` + ValidatorIndex string `json:"validator_index"` +} + +// SubmitSignedVoluntaryExit submits a signed voluntary exit to the beacon node. +func (c *Client) SubmitSignedVoluntaryExit(ctx context.Context, epoch beaconcommon.Epoch, validatorIdx uint64, signature string) (string, error) { + resp, err := c.submitSignedVoluntaryExit(ctx, epoch, validatorIdx, signature) + if err != nil { + c.logger.WithError(err).Errorf("SubmitSignedVoluntaryExit failed") + } + + return resp, err +} + +func (c *Client) submitSignedVoluntaryExit(ctx context.Context, epoch beaconcommon.Epoch, validatorIdx uint64, signature string) (string, error) { + reqBody := newSignedVoluntaryExit(epoch, validatorIdx, signature) + req, err := newSignedVoluntaryExitsRequest(ctx, reqBody) + if err != nil { + return "", autorest.NewErrorWithError(err, "eth2http.Client", "SubmitSignedVoluntaryExit", nil, "Failure preparing request") + } + + resp, err := c.client.Do(req) + if err != nil { + return "", autorest.NewErrorWithError(err, "eth2http.Client", "SubmitSignedVoluntaryExit", resp, "Failure sending request") + } + + ve, err := inspectSubmitSignedVoluntaryExitResponse(resp) + if err != nil { + return "", autorest.NewErrorWithError(err, "eth2http.Client", "SubmitSignedVoluntaryExit", resp, "Invalid response") + } + + return ve, nil +} + +func newSignedVoluntaryExitsRequest(ctx context.Context, signedVoluntaryExit *signedVoluntaryExit) (*http.Request, error) { + return autorest.CreatePreparer( + autorest.AsPost(), + autorest.AsJSON(), + autorest.WithJSON(signedVoluntaryExit), + autorest.WithPath("/eth/v1/beacon/pool/voluntary_exits"), + ).Prepare(newRequest(ctx)) +} + +func newSignedVoluntaryExit(epoch beaconcommon.Epoch, validatorIdx uint64, signature string) *signedVoluntaryExit { + return &signedVoluntaryExit{ + Message: message{ + Epoch: epoch, + ValidatorIndex: strconv.Itoa(int(validatorIdx)), + }, + Signature: signature, + } +} + +func inspectSubmitSignedVoluntaryExitResponse(resp *http.Response) (string, error) { + var msg string + err := inspectResponse(resp, &msg) + if err != nil { + return "", err + } + + return msg, nil +} diff --git a/ethereum/consensus/client/mock/client.go b/ethereum/consensus/client/mock/client.go index fde3fc7..d1e722b 100644 --- a/ethereum/consensus/client/mock/client.go +++ b/ethereum/consensus/client/mock/client.go @@ -338,6 +338,21 @@ func (mr *MockClientMockRecorder) GetVoluntaryExits(ctx interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVoluntaryExits", reflect.TypeOf((*MockClient)(nil).GetVoluntaryExits), ctx) } +// SubmitSignedVoluntaryExit mocks base method. +func (m *MockClient) SubmitSignedVoluntaryExit(ctx context.Context, epoch common.Epoch, validatorIdx uint64, signature string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubmitSignedVoluntaryExit", ctx, epoch, validatorIdx, signature) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubmitSignedVoluntaryExit indicates an expected call of SubmitSignedVoluntaryExit. +func (mr *MockClientMockRecorder) SubmitSignedVoluntaryExit(ctx, epoch, validatorIdx, signature interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitSignedVoluntaryExit", reflect.TypeOf((*MockClient)(nil).SubmitSignedVoluntaryExit), ctx, epoch, validatorIdx, signature) +} + // MockBeaconClient is a mock of BeaconClient interface. type MockBeaconClient struct { ctrl *gomock.Controller @@ -631,6 +646,21 @@ func (mr *MockBeaconClientMockRecorder) GetVoluntaryExits(ctx interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVoluntaryExits", reflect.TypeOf((*MockBeaconClient)(nil).GetVoluntaryExits), ctx) } +// SubmitSignedVoluntaryExit mocks base method. +func (m *MockBeaconClient) SubmitSignedVoluntaryExit(ctx context.Context, epoch common.Epoch, validatorIdx uint64, signature string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubmitSignedVoluntaryExit", ctx, epoch, validatorIdx, signature) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubmitSignedVoluntaryExit indicates an expected call of SubmitSignedVoluntaryExit. +func (mr *MockBeaconClientMockRecorder) SubmitSignedVoluntaryExit(ctx, epoch, validatorIdx, signature interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitSignedVoluntaryExit", reflect.TypeOf((*MockBeaconClient)(nil).SubmitSignedVoluntaryExit), ctx, epoch, validatorIdx, signature) +} + // MockNodeClient is a mock of NodeClient interface. type MockNodeClient struct { ctrl *gomock.Controller