Skip to content

Commit cbda36d

Browse files
committed
Merge branch 'get-graffiti' into graffiti-management
# Conflicts: # validator/client/src/main/java/tech/pegasys/teku/validator/client/restapi/ValidatorRestApi.java
2 parents 40d756e + 0225f09 commit cbda36d

File tree

8 files changed

+125
-62
lines changed

8 files changed

+125
-62
lines changed

.circleci/config.yml

+2-10
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ commands:
148148
name: "Publish Docker Images"
149149
command: |
150150
docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}"
151-
./gradlew --no-daemon --parallel "-Pbranch=${CIRCLE_BRANCH} -PincludeCommitHash=<< pipeline.parameters.include_commit_hash >>" uploadDocker
151+
./gradlew --no-daemon --parallel "-Pbranch=${CIRCLE_BRANCH} -PincludeCommitHashInDockerTag=<< pipeline.parameters.include_commit_hash_in_docker_tag >>" uploadDocker
152152
153153
notify:
154154
description: "Notify Slack"
@@ -159,7 +159,7 @@ commands:
159159
only_for_branches: 'master'
160160

161161
parameters:
162-
include_commit_hash:
162+
include_commit_hash_in_docker_tag:
163163
type: boolean
164164
default: false
165165

@@ -437,10 +437,6 @@ jobs:
437437
- notify
438438

439439
publishDockerAmd64:
440-
parameters:
441-
include_commit_hash:
442-
type: boolean
443-
default: false
444440
executor: machine_executor_amd64
445441
steps:
446442
- prepare
@@ -451,10 +447,6 @@ jobs:
451447
- notify
452448

453449
publishDockerArm64:
454-
parameters:
455-
include_commit_hash:
456-
type: boolean
457-
default: false
458450
executor: machine_executor_arm64
459451
steps:
460452
- prepare

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ the [releases page](https://github.com/Consensys/teku/releases).
1818
- Added hidden option `--Xdeposit-contract-logs-syncing-enabled` to allow disabling the syncing of the deposit contract logs from the EL. This is useful when running a non-validating node. It is advisable to be used alongside with `--Xeth1-missing-deposits-event-logging-enabled=false` to avoid unnecessary logging of missing deposits.
1919
- Updated the bootnodes for Chiado and Gnosis networks.
2020
- Added hidden option `--Xp2p-dumps-to-file-enabled` to enable saving p2p dumps to file.
21+
- Added GET `/eth/v1/validator/{pubkey}/graffiti` validator API.
2122

2223
### Bug Fixes

build.gradle

+8-14
Original file line numberDiff line numberDiff line change
@@ -532,24 +532,16 @@ def executableAndArg = System.getProperty('os.name').toLowerCase().contains('win
532532
task distDocker {
533533
dependsOn dockerDistUntar
534534
def dockerBuildVersion = 'develop'
535-
536-
if (project.hasProperty('includeCommitHash') && project.property('includeCommitHash').toBoolean()) {
537-
def commitHash = new ByteArrayOutputStream()
538-
exec {
539-
commandLine 'git', 'rev-parse', '--short', 'HEAD'
540-
standardOutput = commitHash
541-
}
542-
dockerBuildVersion += '-' + commitHash
543-
}
544-
545535
doLast {
536+
def includeCommitHashInDockerTag = project.hasProperty('includeCommitHashInDockerTag') && project.property('includeCommitHashInDockerTag').toBoolean()
537+
def commitHashTag = includeCommitHashInDockerTag ? '-' + grgit.head().getAbbreviatedId() : ''
546538
for (def variant in dockerJdkVariants) {
547539
copy {
548540
from file("${projectDir}/docker/${variant}/Dockerfile")
549541
into(dockerBuildDir)
550542
}
551543
exec {
552-
def image = "${dockerImage}:${dockerBuildVersion}-${variant}"
544+
def image = "${dockerImage}:${dockerBuildVersion}-${variant}${commitHashTag}"
553545
workingDir dockerBuildDir
554546
executable executableAndArg[0]
555547
args executableAndArg[1], "docker build --pull --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${getCheckedOutGitCommitHash()} -t ${image} ."
@@ -558,7 +550,7 @@ task distDocker {
558550
// tag the "default" (which is the variant in the zero position)
559551
exec {
560552
executable executableAndArg[0]
561-
args executableAndArg[1], "docker tag ${dockerImage}:${dockerBuildVersion}-${dockerJdkVariants[0]} ${dockerImage}:${dockerBuildVersion}"
553+
args executableAndArg[1], "docker tag ${dockerImage}:${dockerBuildVersion}-${dockerJdkVariants[0]}${commitHashTag} ${dockerImage}:${dockerBuildVersion}${commitHashTag}"
562554
}
563555
}
564556
}
@@ -580,12 +572,14 @@ task uploadDocker {
580572
}
581573

582574
doLast {
575+
def includeCommitHashInDockerTag = project.hasProperty('includeCommitHashInDockerTag') && project.property('includeCommitHashInDockerTag').toBoolean()
576+
def commitHashTag = includeCommitHashInDockerTag ? '-' + grgit.head().getAbbreviatedId() : ''
583577
for (def variant in dockerJdkVariants) {
584578
def tags = ""
585-
versionPrefixes.forEach { prefix -> tags += "-t ${dockerImage}:${prefix.trim()}-${variant}-${architecture} "}
579+
versionPrefixes.forEach { prefix -> tags += "-t ${dockerImage}:${prefix.trim()}-${variant}-${architecture}${commitHashTag} "}
586580

587581
if (variant == dockerJdkVariants[0]) {
588-
versionPrefixes.forEach { prefix -> tags += "-t ${dockerImage}:${prefix.trim()}-${architecture} "}
582+
versionPrefixes.forEach { prefix -> tags += "-t ${dockerImage}:${prefix.trim()}-${architecture}${commitHashTag} "}
589583
}
590584

591585
copy {

storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -336,16 +336,12 @@ public Optional<UInt64> getOptimisticTransitionBlockSlot() {
336336

337337
@Override
338338
public Optional<Bytes> getBlobSidecar(final SlotAndBlockRootAndBlobIndex key) {
339-
return db.get(
340-
schema.getColumnBlobSidecarBySlotRootBlobIndex(),
341-
new SlotAndBlockRootAndBlobIndex(key.getSlot(), key.getBlockRoot(), key.getBlobIndex()));
339+
return db.get(schema.getColumnBlobSidecarBySlotRootBlobIndex(), key);
342340
}
343341

344342
@Override
345343
public Optional<Bytes> getNonCanonicalBlobSidecar(final SlotAndBlockRootAndBlobIndex key) {
346-
return db.get(
347-
schema.getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex(),
348-
new SlotAndBlockRootAndBlobIndex(key.getSlot(), key.getBlockRoot(), key.getBlobIndex()));
344+
return db.get(schema.getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex(), key);
349345
}
350346

351347
@MustBeClosed

validator/client/src/main/java/tech/pegasys/teku/validator/client/restapi/ValidatorRestApi.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public static RestApi create(
134134
.endpoint(new DeleteFeeRecipient(proposerConfigManager))
135135
.endpoint(new DeleteGasLimit(proposerConfigManager))
136136
.endpoint(new PostVoluntaryExit(voluntaryExitDataProvider))
137-
.endpoint(new GetGraffiti())
137+
.endpoint(new GetGraffiti(keyManager))
138138
.endpoint(new SetGraffiti(keyManager, graffitiManager))
139139
.endpoint(new DeleteGraffiti(keyManager, graffitiManager))
140140
.sslCertificate(config.getRestApiKeystoreFile(), config.getRestApiKeystorePasswordFile())

validator/client/src/main/java/tech/pegasys/teku/validator/client/restapi/apis/GetGraffiti.java

+44-15
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,51 @@
1414
package tech.pegasys.teku.validator.client.restapi.apis;
1515

1616
import static tech.pegasys.teku.ethereum.json.types.SharedApiTypes.PUBKEY_API_TYPE;
17+
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND;
1718
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
18-
import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.STRING_TYPE;
1919
import static tech.pegasys.teku.validator.client.restapi.ValidatorRestApi.TAG_GRAFFITI;
2020
import static tech.pegasys.teku.validator.client.restapi.ValidatorTypes.PARAM_PUBKEY_TYPE;
2121

2222
import com.fasterxml.jackson.core.JsonProcessingException;
23+
import java.nio.charset.StandardCharsets;
2324
import java.util.Objects;
2425
import java.util.Optional;
2526
import java.util.function.Function;
26-
import org.apache.commons.lang3.NotImplementedException;
27+
import org.apache.tuweni.bytes.Bytes;
28+
import org.apache.tuweni.bytes.Bytes32;
2729
import tech.pegasys.teku.bls.BLSPublicKey;
30+
import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition;
2831
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
2932
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
3033
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
3134
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
35+
import tech.pegasys.teku.validator.api.Bytes32Parser;
36+
import tech.pegasys.teku.validator.client.KeyManager;
37+
import tech.pegasys.teku.validator.client.Validator;
3238

3339
public class GetGraffiti extends RestApiEndpoint {
34-
public static final String ROUTE = "/eth/v1/validator/{pubkey}/graffiti";
40+
static final String ROUTE = "/eth/v1/validator/{pubkey}/graffiti";
41+
private final KeyManager keyManager;
3542

36-
private static final SerializableTypeDefinition<GraffitiResponse> GRAFFITI_TYPE =
43+
public static final DeserializableTypeDefinition<Bytes32> GRAFFITI_TYPE =
44+
DeserializableTypeDefinition.string(Bytes32.class)
45+
.formatter(GetGraffiti::processGraffitiString)
46+
.parser(Bytes32Parser::toBytes32)
47+
.build();
48+
49+
private static final SerializableTypeDefinition<GraffitiResponse> GRAFFITI_RESPONSE_TYPE =
3750
SerializableTypeDefinition.object(GraffitiResponse.class)
3851
.withOptionalField("pubkey", PUBKEY_API_TYPE, GraffitiResponse::getPublicKey)
39-
.withField("graffiti", STRING_TYPE, GraffitiResponse::getGraffiti)
52+
.withField("graffiti", GRAFFITI_TYPE, GraffitiResponse::getGraffiti)
4053
.build();
4154

4255
private static final SerializableTypeDefinition<GraffitiResponse> RESPONSE_TYPE =
4356
SerializableTypeDefinition.object(GraffitiResponse.class)
4457
.name("GraffitiResponse")
45-
.withField("data", GRAFFITI_TYPE, Function.identity())
58+
.withField("data", GRAFFITI_RESPONSE_TYPE, Function.identity())
4659
.build();
4760

48-
public GetGraffiti() {
61+
public GetGraffiti(final KeyManager keyManager) {
4962
super(
5063
EndpointMetadata.get(ROUTE)
5164
.operationId("getGraffiti")
@@ -58,20 +71,36 @@ public GetGraffiti() {
5871
.response(SC_OK, "Success response", RESPONSE_TYPE)
5972
.withAuthenticationResponses()
6073
.withNotFoundResponse()
61-
.withNotImplementedResponse()
6274
.build());
75+
this.keyManager = keyManager;
6376
}
6477

6578
@Override
66-
public void handleRequest(RestApiRequest request) throws JsonProcessingException {
67-
throw new NotImplementedException("Not implemented");
79+
public void handleRequest(final RestApiRequest request) throws JsonProcessingException {
80+
final BLSPublicKey publicKey = request.getPathParameter(PARAM_PUBKEY_TYPE);
81+
82+
final Optional<Validator> maybeValidator = keyManager.getValidatorByPublicKey(publicKey);
83+
if (maybeValidator.isEmpty()) {
84+
request.respondError(SC_NOT_FOUND, "Validator not found");
85+
return;
86+
}
87+
88+
request.respondOk(new GraffitiResponse(publicKey, maybeValidator.get().getGraffiti()));
89+
}
90+
91+
private static String processGraffitiString(final Bytes32 graffiti) {
92+
return new String(graffiti.toArrayUnsafe(), StandardCharsets.UTF_8).strip().replace("\0", "");
6893
}
6994

7095
static class GraffitiResponse {
7196
private final Optional<BLSPublicKey> publicKey;
72-
private final String graffiti;
97+
private final Bytes32 graffiti;
98+
99+
GraffitiResponse(final BLSPublicKey publicKey, final Optional<Bytes32> graffiti) {
100+
this(publicKey, graffiti.orElse(Bytes32Parser.toBytes32(Bytes.EMPTY.toArray())));
101+
}
73102

74-
GraffitiResponse(final BLSPublicKey publicKey, final String graffiti) {
103+
GraffitiResponse(final BLSPublicKey publicKey, final Bytes32 graffiti) {
75104
this.publicKey = Optional.of(publicKey);
76105
this.graffiti = graffiti;
77106
}
@@ -80,19 +109,19 @@ Optional<BLSPublicKey> getPublicKey() {
80109
return publicKey;
81110
}
82111

83-
String getGraffiti() {
112+
Bytes32 getGraffiti() {
84113
return graffiti;
85114
}
86115

87116
@Override
88-
public boolean equals(Object o) {
117+
public boolean equals(final Object o) {
89118
if (this == o) {
90119
return true;
91120
}
92121
if (o == null || getClass() != o.getClass()) {
93122
return false;
94123
}
95-
GraffitiResponse that = (GraffitiResponse) o;
124+
final GraffitiResponse that = (GraffitiResponse) o;
96125
return Objects.equals(publicKey, that.publicKey) && Objects.equals(graffiti, that.graffiti);
97126
}
98127

validator/client/src/test/java/tech/pegasys/teku/validator/client/restapi/apis/GetGraffitiTest.java

+67-6
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,76 @@
1414
package tech.pegasys.teku.validator.client.restapi.apis;
1515

1616
import static org.assertj.core.api.Assertions.assertThat;
17+
import static org.mockito.ArgumentMatchers.any;
18+
import static org.mockito.ArgumentMatchers.eq;
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.when;
1721
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST;
1822
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_FORBIDDEN;
1923
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR;
20-
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_IMPLEMENTED;
24+
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND;
2125
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
2226
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_UNAUTHORIZED;
2327
import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata;
2428
import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse;
29+
import static tech.pegasys.teku.spec.generator.signatures.NoOpLocalSigner.NO_OP_SIGNER;
2530

2631
import com.fasterxml.jackson.core.JsonProcessingException;
32+
import java.io.IOException;
33+
import java.util.Optional;
34+
import org.apache.tuweni.bytes.Bytes32;
2735
import org.junit.jupiter.api.Test;
36+
import tech.pegasys.teku.bls.BLSPublicKey;
37+
import tech.pegasys.teku.infrastructure.http.HttpErrorResponse;
38+
import tech.pegasys.teku.infrastructure.restapi.StubRestApiRequest;
2839
import tech.pegasys.teku.spec.TestSpecFactory;
2940
import tech.pegasys.teku.spec.util.DataStructureUtil;
41+
import tech.pegasys.teku.validator.api.Bytes32Parser;
42+
import tech.pegasys.teku.validator.api.GraffitiProvider;
43+
import tech.pegasys.teku.validator.client.OwnedKeyManager;
44+
import tech.pegasys.teku.validator.client.Validator;
3045

3146
class GetGraffitiTest {
32-
private final GetGraffiti handler = new GetGraffiti();
47+
private final OwnedKeyManager keyManager = mock(OwnedKeyManager.class);
48+
private final GetGraffiti handler = new GetGraffiti(keyManager);
49+
private StubRestApiRequest request;
50+
51+
final String stringGraffiti = "Test graffiti";
52+
final Bytes32 bytesGraffiti = Bytes32Parser.toBytes32(stringGraffiti);
3353

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

57+
@Test
58+
void shouldGetGraffiti() throws JsonProcessingException {
59+
checkGraffiti(Optional.of(bytesGraffiti));
60+
}
61+
62+
@Test
63+
void shouldGetEmptyGraffiti() throws JsonProcessingException {
64+
checkGraffiti(Optional.empty());
65+
}
66+
67+
@Test
68+
void shouldHandleValidatorNotFound() throws IOException {
69+
request =
70+
StubRestApiRequest.builder()
71+
.metadata(handler.getMetadata())
72+
.pathParameter("pubkey", dataStructureUtil.randomPublicKey().toHexString())
73+
.build();
74+
75+
when(keyManager.getValidatorByPublicKey(any())).thenReturn(Optional.empty());
76+
77+
handler.handleRequest(request);
78+
assertThat(request.getResponseCode()).isEqualTo(SC_NOT_FOUND);
79+
assertThat(request.getResponseBody())
80+
.isEqualTo(new HttpErrorResponse(SC_NOT_FOUND, "Validator not found"));
81+
}
82+
3783
@Test
3884
void metadata_shouldHandle200() throws JsonProcessingException {
3985
final GetGraffiti.GraffitiResponse response =
40-
new GetGraffiti.GraffitiResponse(dataStructureUtil.randomPublicKey(), "Test graffiti");
86+
new GetGraffiti.GraffitiResponse(dataStructureUtil.randomPublicKey(), bytesGraffiti);
4187
final String responseData = getResponseStringFromMetadata(handler, SC_OK, response);
4288
assertThat(responseData)
4389
.isEqualTo(
@@ -66,8 +112,23 @@ void metadata_shouldHandle500() throws JsonProcessingException {
66112
verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR);
67113
}
68114

69-
@Test
70-
void metadata_shouldHandle501() throws JsonProcessingException {
71-
verifyMetadataErrorResponse(handler, SC_NOT_IMPLEMENTED);
115+
private void checkGraffiti(final Optional<Bytes32> graffiti) throws JsonProcessingException {
116+
final GraffitiProvider provider = () -> graffiti;
117+
final BLSPublicKey publicKey = dataStructureUtil.randomPublicKey();
118+
request =
119+
StubRestApiRequest.builder()
120+
.metadata(handler.getMetadata())
121+
.pathParameter("pubkey", publicKey.toHexString())
122+
.build();
123+
124+
final Validator validator = new Validator(publicKey, NO_OP_SIGNER, provider);
125+
when(keyManager.getValidatorByPublicKey(eq(publicKey))).thenReturn(Optional.of(validator));
126+
127+
handler.handleRequest(request);
128+
129+
final GetGraffiti.GraffitiResponse expectedResponse =
130+
new GetGraffiti.GraffitiResponse(publicKey, graffiti);
131+
assertThat(request.getResponseCode()).isEqualTo(SC_OK);
132+
assertThat(request.getResponseBody()).isEqualTo(expectedResponse);
72133
}
73134
}

validator/client/src/test/resources/tech/pegasys/teku/validator/client/restapi/paths/_eth_v1_validator_{pubkey}_graffiti.json

-10
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,6 @@
5858
}
5959
}
6060
},
61-
"501" : {
62-
"description" : "Not implemented",
63-
"content" : {
64-
"application/json" : {
65-
"schema" : {
66-
"$ref" : "#/components/schemas/HttpErrorResponse"
67-
}
68-
}
69-
}
70-
},
7161
"400" : {
7262
"description" : "The request could not be processed, check the response for more information.",
7363
"content" : {

0 commit comments

Comments
 (0)