diff --git a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java index 04cbd0713..b38c0a7ff 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -25,6 +25,7 @@ import tech.pegasys.web3signer.core.config.BaseConfig; import tech.pegasys.web3signer.core.metrics.SlashingProtectionMetrics; import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; +import tech.pegasys.web3signer.core.service.http.handlers.HighWatermarkHandler; import tech.pegasys.web3signer.core.service.http.handlers.LogErrorHandler; import tech.pegasys.web3signer.core.service.http.handlers.keymanager.delete.DeleteKeystoresHandler; import tech.pegasys.web3signer.core.service.http.handlers.keymanager.imports.ImportKeystoresHandler; @@ -90,6 +91,7 @@ public class Eth2Runner extends Runner { public static final String KEYSTORES_PATH = "/eth/v1/keystores"; public static final String PUBLIC_KEYS_PATH = "/api/v1/eth2/publicKeys"; public static final String SIGN_PATH = "/api/v1/eth2/sign/:identifier"; + public static final String HIGH_WATERMARK_PATH = "/api/v1/eth2/highWatermark"; private static final Logger LOG = LogManager.getLogger(); private final Optional slashingProtectionContext; @@ -171,6 +173,13 @@ private void registerEth2Routes( addReloadHandler(router, List.of(blsSignerProvider), errorHandler); + slashingProtectionContext.ifPresent( + protectionContext -> + router + .route(HttpMethod.GET, HIGH_WATERMARK_PATH) + .handler(new HighWatermarkHandler(protectionContext.getSlashingProtection())) + .failureHandler(errorHandler)); + if (isKeyManagerApiEnabled) { router .route(HttpMethod.GET, KEYSTORES_PATH) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/HighWatermarkHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/HighWatermarkHandler.java new file mode 100644 index 000000000..29540dd74 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/HighWatermarkHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers; + +import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE; +import static tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.JSON_UTF_8; + +import tech.pegasys.web3signer.slashingprotection.SlashingProtection; + +import java.util.Map; + +import io.vertx.core.Handler; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.RoutingContext; + +public class HighWatermarkHandler implements Handler { + private final SlashingProtection slashingProtection; + + public HighWatermarkHandler(final SlashingProtection slashingProtection) { + this.slashingProtection = slashingProtection; + } + + @Override + public void handle(final RoutingContext context) { + JsonObject response = + slashingProtection + .getHighWatermark() + .map( + hw -> + new JsonObject( + Map.of( + "epoch", String.valueOf(hw.getEpoch()), + "slot", String.valueOf(hw.getSlot())))) + .orElse(new JsonObject()); + + context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(response.encode()); + } +} diff --git a/core/src/main/resources/openapi-specs/eth2/signing/paths/high_watermark.yaml b/core/src/main/resources/openapi-specs/eth2/signing/paths/high_watermark.yaml new file mode 100644 index 000000000..11062da05 --- /dev/null +++ b/core/src/main/resources/openapi-specs/eth2/signing/paths/high_watermark.yaml @@ -0,0 +1,24 @@ +get: + tags: + - 'High Watermark' + summary: 'The High Watermark epoch and slot applicable to all validators' + description: 'Returns the uint64 epoch and slot of the high watermark. Signing of attestations or blocks are prevented at or beyond this high watermark' + operationId: 'HIGH_WATERMARK' + responses: + '200': + description: 'high watermark' + content: + application/json: + schema: + type: "object" + properties: + epoch: + type: "string" + format: "uint64" + slot: + type: "string" + format: "uint64" + '400': + description: 'Bad request format' + '500': + description: 'Internal Web3Signer server error' \ No newline at end of file diff --git a/core/src/main/resources/openapi-specs/eth2/web3signer.yaml b/core/src/main/resources/openapi-specs/eth2/web3signer.yaml index 02921d42f..2fed18419 100644 --- a/core/src/main/resources/openapi-specs/eth2/web3signer.yaml +++ b/core/src/main/resources/openapi-specs/eth2/web3signer.yaml @@ -16,6 +16,8 @@ paths: $ref: './signing/paths/sign.yaml' /api/v1/eth2/publicKeys: $ref: './signing/paths/public_keys.yaml' + /api/v1/eth2/highWatermark: + $ref: './signing/paths/high_watermark.yaml' /reload: $ref: './signing/paths/reload.yaml' /upcheck: diff --git a/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/DbSlashingProtection.java b/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/DbSlashingProtection.java index 2c3629063..4247a911e 100644 --- a/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/DbSlashingProtection.java +++ b/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/DbSlashingProtection.java @@ -16,6 +16,7 @@ import static tech.pegasys.web3signer.slashingprotection.DbLocker.lockForValidator; import tech.pegasys.web3signer.slashingprotection.DbLocker.LockType; +import tech.pegasys.web3signer.slashingprotection.dao.HighWatermark; import tech.pegasys.web3signer.slashingprotection.dao.LowWatermarkDao; import tech.pegasys.web3signer.slashingprotection.dao.MetadataDao; import tech.pegasys.web3signer.slashingprotection.dao.SignedAttestationsDao; @@ -274,6 +275,11 @@ public void updateValidatorEnabledStatus(final Bytes publicKey, final boolean en }); } + @Override + public Optional getHighWatermark() { + return jdbi.inTransaction(READ_COMMITTED, metadataDao::findHighWatermark); + } + private boolean isEnabled(final Handle handle, final int validatorId) { return validatorsDao.isEnabled(handle, validatorId); } diff --git a/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/SlashingProtection.java b/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/SlashingProtection.java index 12219eba8..d6e370a76 100644 --- a/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/SlashingProtection.java +++ b/slashing-protection/src/main/java/tech/pegasys/web3signer/slashingprotection/SlashingProtection.java @@ -12,11 +12,13 @@ */ package tech.pegasys.web3signer.slashingprotection; +import tech.pegasys.web3signer.slashingprotection.dao.HighWatermark; import tech.pegasys.web3signer.slashingprotection.interchange.IncrementalExporter; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -49,4 +51,6 @@ boolean maySignBlock( boolean isEnabledValidator(Bytes publicKey); void updateValidatorEnabledStatus(Bytes publicKey, boolean enabled); + + Optional getHighWatermark(); }