Skip to content

Commit a3683f7

Browse files
authored
Create graffiti management (Consensys#8216)
1 parent 533fcc8 commit a3683f7

File tree

25 files changed

+1459
-98
lines changed

25 files changed

+1459
-98
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ the [releases page](https://github.com/Consensys/teku/releases).
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.
2121
- Appends consensus layer (CL) and execution layer (EL) clients' information to the validator graffiti. Check [documentation](https://docs.teku.consensys.io/development/reference/cli#validators-graffiti-client-append-format) for available configuration options.
22+
- Added support for [Graffiti management](https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Graffiti) in the Key Manager API.
2223

2324
### Bug Fixes

teku/src/main/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommand.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ private void initialise() {
426426
externalSignerHttpClientFactory, validatorConfig.getValidatorExternalSignerUrl()),
427427
asyncRunner,
428428
metricsSystem,
429-
dataDirLayout);
429+
dataDirLayout,
430+
(publicKey) -> Optional.empty());
430431

431432
validatorLoader.loadValidators();
432433
final Map<BLSPublicKey, Validator> ownedValidators =

teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugToolsCommand.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import tech.pegasys.teku.spec.datastructures.state.CommitteeAssignment;
4343
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
4444
import tech.pegasys.teku.validator.api.ValidatorApiChannel;
45+
import tech.pegasys.teku.validator.api.noop.NoOpGraffitiManager;
4546
import tech.pegasys.teku.validator.beaconnode.GenesisDataProvider;
4647
import tech.pegasys.teku.validator.client.KeyManager;
4748
import tech.pegasys.teku.validator.client.NoOpKeyManager;
@@ -165,7 +166,8 @@ public int generateSwaggerDocs(
165166
dataDirLayout,
166167
new SystemTimeProvider(),
167168
Optional.empty(),
168-
new DoppelgangerDetectionAlert());
169+
new DoppelgangerDetectionAlert(),
170+
new NoOpGraffitiManager());
169171

170172
if (api.getRestApiDocs().isPresent()) {
171173
final String docs = api.getRestApiDocs().get();

validator/api/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dependencies {
55
implementation project(':infrastructure:events')
66
implementation project(':infrastructure:exceptions')
77
implementation project(':infrastructure:http')
8+
implementation project(':infrastructure:serviceutils')
89
implementation project(':ethereum:execution-types')
910
implementation project(':ethereum:json-types')
1011
implementation project(':ethereum:spec')
@@ -13,6 +14,7 @@ dependencies {
1314

1415
testImplementation testFixtures(project(':infrastructure:bls'))
1516
testImplementation testFixtures(project(':infrastructure:logging'))
17+
testImplementation testFixtures(project(':infrastructure:serviceutils'))
1618
testImplementation testFixtures(project(':ethereum:spec'))
1719
}
1820

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.validator.api;
15+
16+
public class GraffitiManagementException extends RuntimeException {
17+
18+
public GraffitiManagementException(final String message) {
19+
super(message);
20+
}
21+
22+
public GraffitiManagementException(final String message, final Throwable cause) {
23+
super(message, cause);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.validator.api;
15+
16+
import java.io.IOException;
17+
import java.nio.charset.StandardCharsets;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.util.Optional;
21+
import org.apache.logging.log4j.LogManager;
22+
import org.apache.logging.log4j.Logger;
23+
import org.apache.tuweni.bytes.Bytes32;
24+
import tech.pegasys.teku.bls.BLSPublicKey;
25+
import tech.pegasys.teku.service.serviceutils.layout.DataDirLayout;
26+
27+
public class GraffitiManager {
28+
private static final Logger LOG = LogManager.getLogger();
29+
static final String GRAFFITI_DIR = "graffiti";
30+
private final Path directory;
31+
32+
public GraffitiManager(final DataDirLayout dataDirLayout) {
33+
this(dataDirLayout.getValidatorDataDirectory().resolve(GRAFFITI_DIR));
34+
}
35+
36+
public GraffitiManager(final Path directory) {
37+
this.directory = directory;
38+
if (!directory.toFile().exists() && !directory.toFile().mkdirs()) {
39+
throw new IllegalStateException("Unable to create directory for graffiti management.");
40+
}
41+
}
42+
43+
public synchronized void setGraffiti(final BLSPublicKey publicKey, final String graffiti)
44+
throws GraffitiManagementException {
45+
final String strippedGraffiti = graffiti.strip();
46+
final int graffitiSize = strippedGraffiti.getBytes(StandardCharsets.UTF_8).length;
47+
if (graffitiSize > 32) {
48+
throw new IllegalArgumentException(
49+
String.format(
50+
"'%s' converts to %s bytes. Input must be 32 bytes or less.",
51+
strippedGraffiti, graffitiSize));
52+
}
53+
54+
try {
55+
final Path file = directory.resolve(resolveFileName(publicKey));
56+
Files.writeString(file, strippedGraffiti);
57+
} catch (IOException e) {
58+
throw new GraffitiManagementException(
59+
"Unable to update graffiti for validator " + publicKey, e);
60+
}
61+
}
62+
63+
public synchronized void deleteGraffiti(final BLSPublicKey publicKey)
64+
throws GraffitiManagementException {
65+
final Path file = directory.resolve(resolveFileName(publicKey));
66+
if (!file.toFile().exists()) {
67+
return;
68+
}
69+
70+
try {
71+
Files.delete(file);
72+
} catch (IOException e) {
73+
throw new GraffitiManagementException(
74+
"Unable to delete graffiti for validator " + publicKey, e);
75+
}
76+
}
77+
78+
public synchronized Optional<Bytes32> getGraffiti(final BLSPublicKey publicKey)
79+
throws GraffitiManagementException {
80+
final Path filePath = directory.resolve(resolveFileName(publicKey));
81+
if (!filePath.toFile().exists()) {
82+
return Optional.empty();
83+
}
84+
85+
try {
86+
return Optional.of(GraffitiParser.loadFromFile(filePath));
87+
} catch (GraffitiLoaderException | IllegalArgumentException e) {
88+
LOG.error("Loading graffiti from graffiti storage failed.", e);
89+
throw new GraffitiManagementException(
90+
"Unable to retrieve stored graffiti for validator " + publicKey, e);
91+
}
92+
}
93+
94+
private String resolveFileName(final BLSPublicKey publicKey) {
95+
return publicKey.toSSZBytes().toUnprefixedHexString() + ".txt";
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.validator.api;
15+
16+
import java.util.Optional;
17+
import java.util.function.Supplier;
18+
import org.apache.tuweni.bytes.Bytes32;
19+
20+
public class UpdatableGraffitiProvider implements GraffitiProvider {
21+
private final Supplier<Optional<Bytes32>> storageProvider;
22+
private final GraffitiProvider defaultProvider;
23+
24+
public UpdatableGraffitiProvider(
25+
final Supplier<Optional<Bytes32>> storageProvider, final GraffitiProvider defaultProvider) {
26+
this.storageProvider = storageProvider;
27+
this.defaultProvider = defaultProvider;
28+
}
29+
30+
@Override
31+
public Optional<Bytes32> get() {
32+
return getFromStorage().or(defaultProvider::get);
33+
}
34+
35+
private Optional<Bytes32> getFromStorage() {
36+
try {
37+
return storageProvider.get();
38+
} catch (Exception e) {
39+
return Optional.empty();
40+
}
41+
}
42+
43+
/**
44+
* @return graffiti without checking for thrown Exceptions.
45+
*/
46+
public Optional<Bytes32> getUnsafe() {
47+
return storageProvider.get().or(defaultProvider::get);
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.validator.api.noop;
15+
16+
import java.nio.file.Path;
17+
import java.util.Optional;
18+
import org.apache.tuweni.bytes.Bytes32;
19+
import tech.pegasys.teku.bls.BLSPublicKey;
20+
import tech.pegasys.teku.validator.api.GraffitiManager;
21+
22+
public class NoOpGraffitiManager extends GraffitiManager {
23+
public NoOpGraffitiManager() {
24+
super(Path.of("."));
25+
}
26+
27+
@Override
28+
public void setGraffiti(final BLSPublicKey publicKey, final String graffiti) {}
29+
30+
@Override
31+
public void deleteGraffiti(final BLSPublicKey publicKey) {}
32+
33+
@Override
34+
public Optional<Bytes32> getGraffiti(final BLSPublicKey publicKey) {
35+
return Optional.empty();
36+
}
37+
}

0 commit comments

Comments
 (0)