Skip to content

Commit

Permalink
Merge branch 'master' into set-graffiti
Browse files Browse the repository at this point in the history
# Conflicts:
#	validator/client/src/main/java/tech/pegasys/teku/validator/client/restapi/ValidatorRestApi.java
#	validator/client/src/test/resources/tech/pegasys/teku/validator/client/restapi/paths/_eth_v1_validator_{pubkey}_graffiti.json
  • Loading branch information
courtneyeh committed Apr 15, 2024
2 parents ab82225 + 064ce08 commit ec3e547
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import tech.pegasys.teku.validator.client.restapi.apis.DeleteRemoteKeys;
import tech.pegasys.teku.validator.client.restapi.apis.GetFeeRecipient;
import tech.pegasys.teku.validator.client.restapi.apis.GetGasLimit;
import tech.pegasys.teku.validator.client.restapi.apis.GetGraffiti;
import tech.pegasys.teku.validator.client.restapi.apis.GetKeys;
import tech.pegasys.teku.validator.client.restapi.apis.GetRemoteKeys;
import tech.pegasys.teku.validator.client.restapi.apis.PostKeys;
Expand Down Expand Up @@ -130,6 +131,7 @@ public static RestApi create(
.endpoint(new DeleteFeeRecipient(proposerConfigManager))
.endpoint(new DeleteGasLimit(proposerConfigManager))
.endpoint(new PostVoluntaryExit(voluntaryExitDataProvider))
.endpoint(new GetGraffiti())
.endpoint(new SetGraffiti())
.sslCertificate(config.getRestApiKeystoreFile(), config.getRestApiKeystorePasswordFile())
.passwordFilePath(validatorApiBearerFile)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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.validator.client.restapi.apis;

import static tech.pegasys.teku.ethereum.json.types.SharedApiTypes.PUBKEY_API_TYPE;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.STRING_TYPE;
import static tech.pegasys.teku.validator.client.restapi.ValidatorRestApi.TAG_GRAFFITI;
import static tech.pegasys.teku.validator.client.restapi.ValidatorTypes.PARAM_PUBKEY_TYPE;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.apache.commons.lang3.NotImplementedException;
import tech.pegasys.teku.bls.BLSPublicKey;
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 GetGraffiti extends RestApiEndpoint {
public static final String ROUTE = "/eth/v1/validator/{pubkey}/graffiti";

private static final SerializableTypeDefinition<GraffitiResponse> GRAFFITI_TYPE =
SerializableTypeDefinition.object(GraffitiResponse.class)
.withOptionalField("pubkey", PUBKEY_API_TYPE, GraffitiResponse::getPublicKey)
.withField("graffiti", STRING_TYPE, GraffitiResponse::getGraffiti)
.build();

private static final SerializableTypeDefinition<GraffitiResponse> RESPONSE_TYPE =
SerializableTypeDefinition.object(GraffitiResponse.class)
.name("GraffitiResponse")
.withField("data", GRAFFITI_TYPE, Function.identity())
.build();

public GetGraffiti() {
super(
EndpointMetadata.get(ROUTE)
.operationId("getGraffiti")
.summary("Get Graffiti")
.description(
"Get the graffiti for an individual validator. If no graffiti is set explicitly, returns the process-wide default.")
.tags(TAG_GRAFFITI)
.withBearerAuthSecurity()
.pathParam(PARAM_PUBKEY_TYPE)
.response(SC_OK, "Success response", RESPONSE_TYPE)
.withAuthenticationResponses()
.withNotFoundResponse()
.withNotImplementedResponse()
.build());
}

@Override
public void handleRequest(RestApiRequest request) throws JsonProcessingException {
throw new NotImplementedException("Not implemented");
}

static class GraffitiResponse {
private final Optional<BLSPublicKey> publicKey;
private final String graffiti;

GraffitiResponse(final BLSPublicKey publicKey, final String graffiti) {
this.publicKey = Optional.of(publicKey);
this.graffiti = graffiti;
}

Optional<BLSPublicKey> getPublicKey() {
return publicKey;
}

String getGraffiti() {
return graffiti;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GraffitiResponse that = (GraffitiResponse) o;
return Objects.equals(publicKey, that.publicKey) && Objects.equals(graffiti, that.graffiti);
}

@Override
public int hashCode() {
return Objects.hash(publicKey, graffiti);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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.validator.client.restapi.apis;

import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_FORBIDDEN;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR;
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.HttpStatusCodes.SC_UNAUTHORIZED;
import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata;
import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.Test;
import tech.pegasys.teku.spec.TestSpecFactory;
import tech.pegasys.teku.spec.util.DataStructureUtil;

class GetGraffitiTest {
private final GetGraffiti handler = new GetGraffiti();

private final DataStructureUtil dataStructureUtil =
new DataStructureUtil(TestSpecFactory.createDefault());

@Test
void metadata_shouldHandle200() throws JsonProcessingException {
final GetGraffiti.GraffitiResponse response =
new GetGraffiti.GraffitiResponse(dataStructureUtil.randomPublicKey(), "Test graffiti");
final String responseData = getResponseStringFromMetadata(handler, SC_OK, response);
assertThat(responseData)
.isEqualTo(
"{\"data\":{\"pubkey\":"
+ "\"0xa4654ac3105a58c7634031b5718c4880c87300f72091cfbc69fe490b71d93a671e00e80a388e1ceb8ea1de112003e976\","
+ "\"graffiti\":\"Test graffiti\"}}");
}

@Test
void metadata_shouldHandle400() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_BAD_REQUEST);
}

@Test
void metadata_shouldHandle401() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_UNAUTHORIZED);
}

@Test
void metadata_shouldHandle403() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_FORBIDDEN);
}

@Test
void metadata_shouldHandle500() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR);
}

@Test
void metadata_shouldHandle501() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_NOT_IMPLEMENTED);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,95 @@
{
"get" : {
"tags" : [ "Graffiti" ],
"operationId" : "getGraffiti",
"summary" : "Get Graffiti",
"description" : "Get the graffiti for an individual validator. If no graffiti is set explicitly, returns the process-wide default.",
"parameters" : [ {
"name" : "pubkey",
"required" : true,
"in" : "path",
"schema" : {
"type" : "string",
"pattern" : "^0x[a-fA-F0-9]{96}$",
"example" : "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
}
} ],
"security" : [ {
"bearerAuth" : [ ]
} ],
"responses" : {
"200" : {
"description" : "Success response",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/GraffitiResponse"
}
}
}
},
"401" : {
"description" : "Unauthorized, no token is found",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"403" : {
"description" : "Forbidden, a token is found but is invalid",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"404" : {
"description" : "Not found",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"501" : {
"description" : "Not implemented",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"400" : {
"description" : "The request could not be processed, check the response for more information.",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"500" : {
"description" : "Internal server error",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
}
}
},
"post" : {
"tags" : [ "Graffiti" ],
"operationId" : "setGraffiti",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title" : "GraffitiResponse",
"type" : "object",
"required" : [ "data" ],
"properties" : {
"data" : {
"type" : "object",
"required" : [ "graffiti" ],
"properties" : {
"pubkey" : {
"$ref" : "#/components/schemas/Pubkey"
},
"graffiti" : {
"type" : "string"
}
}
}
}
}

0 comments on commit ec3e547

Please sign in to comment.