Skip to content

Commit

Permalink
Implementing /eth/v1/validator/sync_committee_selections (Consensys#8019
Browse files Browse the repository at this point in the history
)
  • Loading branch information
lucassaldanha authored Feb 26, 2024
1 parent 3313717 commit 9c7f3df
Show file tree
Hide file tree
Showing 20 changed files with 611 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof;
import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties;
import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuty;
import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof;
import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance;
import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformanceFactory;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
Expand Down Expand Up @@ -840,4 +841,10 @@ public SafeFuture<Optional<List<BeaconCommitteeSelectionProof>>> getBeaconCommit
final List<BeaconCommitteeSelectionProof> requests) {
throw new UnsupportedOperationException("This method is not implemented by the Beacon Node");
}

@Override
public SafeFuture<Optional<List<SyncCommitteeSelectionProof>>> getSyncCommitteeSelectionProof(
final List<SyncCommitteeSelectionProof> requests) {
throw new UnsupportedOperationException("This method is not implemented by the Beacon Node");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,12 @@ public void getBeaconCommitteeSelectionProofShouldNotBeImplementedByBeaconNode()
.isInstanceOf(UnsupportedOperationException.class);
}

@Test
public void getSyncCommitteeSelectionProofShouldNotBeImplementedByBeaconNode() {
assertThatThrownBy(() -> validatorApiHandler.getSyncCommitteeSelectionProof(List.of()))
.isInstanceOf(UnsupportedOperationException.class);
}

private boolean validatorIsLive(
List<ValidatorLivenessAtEpoch> validatorLivenessAtEpochs, UInt64 validatorIndex) {
return validatorLivenessAtEpochs.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"post" : {
"tags" : [ "Validator" ],
"operationId" : "submitSyncCommitteeSelections",
"summary" : "Determine if a distributed validator has been selected to make a sync committee contribution",
"description" : "Submit sync committee selections to a DVT middleware client. It returns the threshold aggregated sync committee selection. This endpoint should be used by a validator client running as part of a distributed validator cluster, and is implemented by a distributed validator middleware client. This endpoint is used to exchange partial selection proof slot signatures for combined/aggregated selection proofs to allow a validator client to correctly determine if one of its validators has been selected to perform a sync committee contribution (sync aggregation) duty in this slot. Consensus clients need not support this endpoint and may return a 501.",
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/SyncCommitteeSelectionProof"
}
}
}
}
},
"responses" : {
"200" : {
"description" : "Returns the threshold aggregated sync committee selection proofs.",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/PostSyncCommitteeSelectionsResponse"
}
}
}
},
"400" : {
"description" : "Invalid request syntax.",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"500" : {
"description" : "Internal server error",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"501" : {
"description" : "Not implemented",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"503" : {
"description" : "Beacon node is currently syncing and not serving requests.",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title" : "PostSyncCommitteeSelectionsResponse",
"type" : "object",
"required" : [ "data" ],
"properties" : {
"data" : {
"type" : "array",
"items" : {
"$ref" : "#/components/schemas/SyncCommitteeSelectionProof"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"title" : "SyncCommitteeSelectionProof",
"type" : "object",
"required" : [ "validator_index", "slot", "subcommittee_index", "selection_proof" ],
"properties" : {
"validator_index" : {
"type" : "string",
"description" : "integer string",
"example" : "1",
"format" : "integer"
},
"slot" : {
"type" : "string",
"description" : "unsigned 64 bit integer",
"example" : "1",
"format" : "uint64"
},
"subcommittee_index" : {
"type" : "string",
"description" : "integer string",
"example" : "1",
"format" : "integer"
},
"selection_proof" : {
"type" : "string"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostPrepareBeaconProposer;
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostRegisterValidator;
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSubscribeToBeaconCommitteeSubnet;
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncCommitteeSelections;
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncCommitteeSubscriptions;
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncDuties;
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostValidatorLiveness;
Expand Down Expand Up @@ -278,6 +279,7 @@ private static RestApi create(
.endpoint(new PostRegisterValidator(dataProvider))
// Obol DVT Methods
.endpoint(new PostBeaconCommitteeSelections())
.endpoint(new PostSyncCommitteeSelections())
// Config Handlers
.endpoint(
new GetDepositContract(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright Consensys Software Inc., 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.beaconrestapi.handlers.v1.validator;

import static tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof.SYNC_COMMITTEE_SELECTION_PROOF;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_IMPLEMENTED;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR;
import static tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition.listOf;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof;
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;

public class PostSyncCommitteeSelections extends RestApiEndpoint {

public static final String ROUTE = "/eth/v1/validator/sync_committee_selections";

private static final SerializableTypeDefinition<List<SyncCommitteeSelectionProof>> RESPONSE_TYPE =
SerializableTypeDefinition.<List<SyncCommitteeSelectionProof>>object()
.name("PostSyncCommitteeSelectionsResponse")
.withField("data", listOf(SYNC_COMMITTEE_SELECTION_PROOF), Function.identity())
.build();

public PostSyncCommitteeSelections() {
super(
EndpointMetadata.post(ROUTE)
.operationId("submitSyncCommitteeSelections")
.summary(
"Determine if a distributed validator has been selected to make a sync committee contribution")
.description(
"Submit sync committee selections to a DVT middleware client. It returns the threshold aggregated "
+ "sync committee selection. This endpoint should be used by a validator client running as part "
+ "of a distributed validator cluster, and is implemented by a distributed validator middleware "
+ "client. This endpoint is used to exchange partial selection proof slot signatures for "
+ "combined/aggregated selection proofs to allow a validator client to correctly determine if one"
+ " of its validators has been selected to perform a sync committee contribution (sync "
+ "aggregation) duty in this slot. Consensus clients need not support this endpoint and may "
+ "return a 501.")
.tags(TAG_VALIDATOR)
.requestBodyType(listOf(SYNC_COMMITTEE_SELECTION_PROOF))
.response(
SC_OK,
"Returns the threshold aggregated sync committee selection proofs.",
RESPONSE_TYPE)
.withBadRequestResponse(Optional.of("Invalid request syntax."))
.withInternalErrorResponse()
.withNotImplementedResponse()
.withServiceUnavailableResponse()
.build());
}

@Override
public void handleRequest(final RestApiRequest request) throws JsonProcessingException {
request.respondError(SC_NOT_IMPLEMENTED, "Method not implemented by the Beacon Node");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright Consensys Software Inc., 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.ethereum.json.types.validator;

import java.util.Objects;
import tech.pegasys.teku.infrastructure.json.types.CoreTypes;
import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;

public class SyncCommitteeSelectionProof {

public static final DeserializableTypeDefinition<SyncCommitteeSelectionProof>
SYNC_COMMITTEE_SELECTION_PROOF =
DeserializableTypeDefinition.object(
SyncCommitteeSelectionProof.class, SyncCommitteeSelectionProof.Builder.class)
.name("SyncCommitteeSelectionProof")
.initializer(SyncCommitteeSelectionProof::builder)
.finisher(SyncCommitteeSelectionProof.Builder::build)
.withField(
"validator_index",
CoreTypes.INTEGER_TYPE,
SyncCommitteeSelectionProof::getValidatorIndex,
SyncCommitteeSelectionProof.Builder::validatorIndex)
.withField(
"slot",
CoreTypes.UINT64_TYPE,
SyncCommitteeSelectionProof::getSlot,
SyncCommitteeSelectionProof.Builder::slot)
.withField(
"subcommittee_index",
CoreTypes.INTEGER_TYPE,
SyncCommitteeSelectionProof::getSubcommitteeIndex,
SyncCommitteeSelectionProof.Builder::subcommitteeIndex)
.withField(
"selection_proof",
CoreTypes.STRING_TYPE,
SyncCommitteeSelectionProof::getSelectionProof,
SyncCommitteeSelectionProof.Builder::selectionProof)
.build();

private final int validatorIndex;
private final UInt64 slot;
private final int subcommitteeIndex;
private final String selectionProof;

private SyncCommitteeSelectionProof(
final int validatorIndex,
final UInt64 slot,
final int subcommitteeIndex,
final String selectionProof) {
this.validatorIndex = validatorIndex;
this.slot = slot;
this.subcommitteeIndex = subcommitteeIndex;
this.selectionProof = selectionProof;
}

public int getValidatorIndex() {
return validatorIndex;
}

public UInt64 getSlot() {
return slot;
}

public int getSubcommitteeIndex() {
return subcommitteeIndex;
}

public String getSelectionProof() {
return selectionProof;
}

public static SyncCommitteeSelectionProof.Builder builder() {
return new SyncCommitteeSelectionProof.Builder();
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final SyncCommitteeSelectionProof that = (SyncCommitteeSelectionProof) o;
return validatorIndex == that.validatorIndex
&& subcommitteeIndex == that.subcommitteeIndex
&& Objects.equals(slot, that.slot)
&& Objects.equals(selectionProof, that.selectionProof);
}

@Override
public int hashCode() {
return Objects.hash(validatorIndex, slot, subcommitteeIndex, selectionProof);
}

public static class Builder {

private int validatorIndex;
private UInt64 slot;
private int subcommitteeIndex;
private String selectionProof;

public Builder validatorIndex(final int validatorIndex) {
this.validatorIndex = validatorIndex;
return this;
}

public Builder subcommitteeIndex(final int subcommitteeIndex) {
this.subcommitteeIndex = subcommitteeIndex;
return this;
}

public Builder slot(final UInt64 slot) {
this.slot = slot;
return this;
}

public Builder selectionProof(final String selectionProof) {
this.selectionProof = selectionProof;
return this;
}

public SyncCommitteeSelectionProof build() {
return new SyncCommitteeSelectionProof(
validatorIndex, slot, subcommitteeIndex, selectionProof);
}
}
}
Loading

0 comments on commit 9c7f3df

Please sign in to comment.