Skip to content

Commit 9c7f3df

Browse files
Implementing /eth/v1/validator/sync_committee_selections (Consensys#8019)
1 parent 3313717 commit 9c7f3df

File tree

20 files changed

+611
-7
lines changed

20 files changed

+611
-7
lines changed

beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java

+7
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import tech.pegasys.teku.ethereum.json.types.validator.BeaconCommitteeSelectionProof;
5151
import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties;
5252
import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuty;
53+
import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof;
5354
import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance;
5455
import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformanceFactory;
5556
import tech.pegasys.teku.infrastructure.async.SafeFuture;
@@ -840,4 +841,10 @@ public SafeFuture<Optional<List<BeaconCommitteeSelectionProof>>> getBeaconCommit
840841
final List<BeaconCommitteeSelectionProof> requests) {
841842
throw new UnsupportedOperationException("This method is not implemented by the Beacon Node");
842843
}
844+
845+
@Override
846+
public SafeFuture<Optional<List<SyncCommitteeSelectionProof>>> getSyncCommitteeSelectionProof(
847+
final List<SyncCommitteeSelectionProof> requests) {
848+
throw new UnsupportedOperationException("This method is not implemented by the Beacon Node");
849+
}
843850
}

beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,12 @@ public void getBeaconCommitteeSelectionProofShouldNotBeImplementedByBeaconNode()
12021202
.isInstanceOf(UnsupportedOperationException.class);
12031203
}
12041204

1205+
@Test
1206+
public void getSyncCommitteeSelectionProofShouldNotBeImplementedByBeaconNode() {
1207+
assertThatThrownBy(() -> validatorApiHandler.getSyncCommitteeSelectionProof(List.of()))
1208+
.isInstanceOf(UnsupportedOperationException.class);
1209+
}
1210+
12051211
private boolean validatorIsLive(
12061212
List<ValidatorLivenessAtEpoch> validatorLivenessAtEpochs, UInt64 validatorIndex) {
12071213
return validatorLivenessAtEpochs.stream()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"post" : {
3+
"tags" : [ "Validator" ],
4+
"operationId" : "submitSyncCommitteeSelections",
5+
"summary" : "Determine if a distributed validator has been selected to make a sync committee contribution",
6+
"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.",
7+
"requestBody" : {
8+
"content" : {
9+
"application/json" : {
10+
"schema" : {
11+
"type" : "array",
12+
"items" : {
13+
"$ref" : "#/components/schemas/SyncCommitteeSelectionProof"
14+
}
15+
}
16+
}
17+
}
18+
},
19+
"responses" : {
20+
"200" : {
21+
"description" : "Returns the threshold aggregated sync committee selection proofs.",
22+
"content" : {
23+
"application/json" : {
24+
"schema" : {
25+
"$ref" : "#/components/schemas/PostSyncCommitteeSelectionsResponse"
26+
}
27+
}
28+
}
29+
},
30+
"400" : {
31+
"description" : "Invalid request syntax.",
32+
"content" : {
33+
"application/json" : {
34+
"schema" : {
35+
"$ref" : "#/components/schemas/HttpErrorResponse"
36+
}
37+
}
38+
}
39+
},
40+
"500" : {
41+
"description" : "Internal server error",
42+
"content" : {
43+
"application/json" : {
44+
"schema" : {
45+
"$ref" : "#/components/schemas/HttpErrorResponse"
46+
}
47+
}
48+
}
49+
},
50+
"501" : {
51+
"description" : "Not implemented",
52+
"content" : {
53+
"application/json" : {
54+
"schema" : {
55+
"$ref" : "#/components/schemas/HttpErrorResponse"
56+
}
57+
}
58+
}
59+
},
60+
"503" : {
61+
"description" : "Beacon node is currently syncing and not serving requests.",
62+
"content" : {
63+
"application/json" : {
64+
"schema" : {
65+
"$ref" : "#/components/schemas/HttpErrorResponse"
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title" : "PostSyncCommitteeSelectionsResponse",
3+
"type" : "object",
4+
"required" : [ "data" ],
5+
"properties" : {
6+
"data" : {
7+
"type" : "array",
8+
"items" : {
9+
"$ref" : "#/components/schemas/SyncCommitteeSelectionProof"
10+
}
11+
}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"title" : "SyncCommitteeSelectionProof",
3+
"type" : "object",
4+
"required" : [ "validator_index", "slot", "subcommittee_index", "selection_proof" ],
5+
"properties" : {
6+
"validator_index" : {
7+
"type" : "string",
8+
"description" : "integer string",
9+
"example" : "1",
10+
"format" : "integer"
11+
},
12+
"slot" : {
13+
"type" : "string",
14+
"description" : "unsigned 64 bit integer",
15+
"example" : "1",
16+
"format" : "uint64"
17+
},
18+
"subcommittee_index" : {
19+
"type" : "string",
20+
"description" : "integer string",
21+
"example" : "1",
22+
"format" : "integer"
23+
},
24+
"selection_proof" : {
25+
"type" : "string"
26+
}
27+
}
28+
}

data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostPrepareBeaconProposer;
9797
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostRegisterValidator;
9898
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSubscribeToBeaconCommitteeSubnet;
99+
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncCommitteeSelections;
99100
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncCommitteeSubscriptions;
100101
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncDuties;
101102
import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostValidatorLiveness;
@@ -278,6 +279,7 @@ private static RestApi create(
278279
.endpoint(new PostRegisterValidator(dataProvider))
279280
// Obol DVT Methods
280281
.endpoint(new PostBeaconCommitteeSelections())
282+
.endpoint(new PostSyncCommitteeSelections())
281283
// Config Handlers
282284
.endpoint(
283285
new GetDepositContract(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2024
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.beaconrestapi.handlers.v1.validator;
15+
16+
import static tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof.SYNC_COMMITTEE_SELECTION_PROOF;
17+
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_IMPLEMENTED;
18+
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
19+
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR;
20+
import static tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition.listOf;
21+
22+
import com.fasterxml.jackson.core.JsonProcessingException;
23+
import java.util.List;
24+
import java.util.Optional;
25+
import java.util.function.Function;
26+
import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof;
27+
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
28+
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
29+
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
30+
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
31+
32+
public class PostSyncCommitteeSelections extends RestApiEndpoint {
33+
34+
public static final String ROUTE = "/eth/v1/validator/sync_committee_selections";
35+
36+
private static final SerializableTypeDefinition<List<SyncCommitteeSelectionProof>> RESPONSE_TYPE =
37+
SerializableTypeDefinition.<List<SyncCommitteeSelectionProof>>object()
38+
.name("PostSyncCommitteeSelectionsResponse")
39+
.withField("data", listOf(SYNC_COMMITTEE_SELECTION_PROOF), Function.identity())
40+
.build();
41+
42+
public PostSyncCommitteeSelections() {
43+
super(
44+
EndpointMetadata.post(ROUTE)
45+
.operationId("submitSyncCommitteeSelections")
46+
.summary(
47+
"Determine if a distributed validator has been selected to make a sync committee contribution")
48+
.description(
49+
"Submit sync committee selections to a DVT middleware client. It returns the threshold aggregated "
50+
+ "sync committee selection. This endpoint should be used by a validator client running as part "
51+
+ "of a distributed validator cluster, and is implemented by a distributed validator middleware "
52+
+ "client. This endpoint is used to exchange partial selection proof slot signatures for "
53+
+ "combined/aggregated selection proofs to allow a validator client to correctly determine if one"
54+
+ " of its validators has been selected to perform a sync committee contribution (sync "
55+
+ "aggregation) duty in this slot. Consensus clients need not support this endpoint and may "
56+
+ "return a 501.")
57+
.tags(TAG_VALIDATOR)
58+
.requestBodyType(listOf(SYNC_COMMITTEE_SELECTION_PROOF))
59+
.response(
60+
SC_OK,
61+
"Returns the threshold aggregated sync committee selection proofs.",
62+
RESPONSE_TYPE)
63+
.withBadRequestResponse(Optional.of("Invalid request syntax."))
64+
.withInternalErrorResponse()
65+
.withNotImplementedResponse()
66+
.withServiceUnavailableResponse()
67+
.build());
68+
}
69+
70+
@Override
71+
public void handleRequest(final RestApiRequest request) throws JsonProcessingException {
72+
request.respondError(SC_NOT_IMPLEMENTED, "Method not implemented by the Beacon Node");
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2024
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.ethereum.json.types.validator;
15+
16+
import java.util.Objects;
17+
import tech.pegasys.teku.infrastructure.json.types.CoreTypes;
18+
import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition;
19+
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
20+
21+
public class SyncCommitteeSelectionProof {
22+
23+
public static final DeserializableTypeDefinition<SyncCommitteeSelectionProof>
24+
SYNC_COMMITTEE_SELECTION_PROOF =
25+
DeserializableTypeDefinition.object(
26+
SyncCommitteeSelectionProof.class, SyncCommitteeSelectionProof.Builder.class)
27+
.name("SyncCommitteeSelectionProof")
28+
.initializer(SyncCommitteeSelectionProof::builder)
29+
.finisher(SyncCommitteeSelectionProof.Builder::build)
30+
.withField(
31+
"validator_index",
32+
CoreTypes.INTEGER_TYPE,
33+
SyncCommitteeSelectionProof::getValidatorIndex,
34+
SyncCommitteeSelectionProof.Builder::validatorIndex)
35+
.withField(
36+
"slot",
37+
CoreTypes.UINT64_TYPE,
38+
SyncCommitteeSelectionProof::getSlot,
39+
SyncCommitteeSelectionProof.Builder::slot)
40+
.withField(
41+
"subcommittee_index",
42+
CoreTypes.INTEGER_TYPE,
43+
SyncCommitteeSelectionProof::getSubcommitteeIndex,
44+
SyncCommitteeSelectionProof.Builder::subcommitteeIndex)
45+
.withField(
46+
"selection_proof",
47+
CoreTypes.STRING_TYPE,
48+
SyncCommitteeSelectionProof::getSelectionProof,
49+
SyncCommitteeSelectionProof.Builder::selectionProof)
50+
.build();
51+
52+
private final int validatorIndex;
53+
private final UInt64 slot;
54+
private final int subcommitteeIndex;
55+
private final String selectionProof;
56+
57+
private SyncCommitteeSelectionProof(
58+
final int validatorIndex,
59+
final UInt64 slot,
60+
final int subcommitteeIndex,
61+
final String selectionProof) {
62+
this.validatorIndex = validatorIndex;
63+
this.slot = slot;
64+
this.subcommitteeIndex = subcommitteeIndex;
65+
this.selectionProof = selectionProof;
66+
}
67+
68+
public int getValidatorIndex() {
69+
return validatorIndex;
70+
}
71+
72+
public UInt64 getSlot() {
73+
return slot;
74+
}
75+
76+
public int getSubcommitteeIndex() {
77+
return subcommitteeIndex;
78+
}
79+
80+
public String getSelectionProof() {
81+
return selectionProof;
82+
}
83+
84+
public static SyncCommitteeSelectionProof.Builder builder() {
85+
return new SyncCommitteeSelectionProof.Builder();
86+
}
87+
88+
@Override
89+
public boolean equals(final Object o) {
90+
if (this == o) {
91+
return true;
92+
}
93+
if (o == null || getClass() != o.getClass()) {
94+
return false;
95+
}
96+
final SyncCommitteeSelectionProof that = (SyncCommitteeSelectionProof) o;
97+
return validatorIndex == that.validatorIndex
98+
&& subcommitteeIndex == that.subcommitteeIndex
99+
&& Objects.equals(slot, that.slot)
100+
&& Objects.equals(selectionProof, that.selectionProof);
101+
}
102+
103+
@Override
104+
public int hashCode() {
105+
return Objects.hash(validatorIndex, slot, subcommitteeIndex, selectionProof);
106+
}
107+
108+
public static class Builder {
109+
110+
private int validatorIndex;
111+
private UInt64 slot;
112+
private int subcommitteeIndex;
113+
private String selectionProof;
114+
115+
public Builder validatorIndex(final int validatorIndex) {
116+
this.validatorIndex = validatorIndex;
117+
return this;
118+
}
119+
120+
public Builder subcommitteeIndex(final int subcommitteeIndex) {
121+
this.subcommitteeIndex = subcommitteeIndex;
122+
return this;
123+
}
124+
125+
public Builder slot(final UInt64 slot) {
126+
this.slot = slot;
127+
return this;
128+
}
129+
130+
public Builder selectionProof(final String selectionProof) {
131+
this.selectionProof = selectionProof;
132+
return this;
133+
}
134+
135+
public SyncCommitteeSelectionProof build() {
136+
return new SyncCommitteeSelectionProof(
137+
validatorIndex, slot, subcommitteeIndex, selectionProof);
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)