diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1c202c3..7632a997e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ ### Features Added - Aws bulk loading for secp256k1 keys in eth1 mode [#889](https://github.com/Consensys/web3signer/pull/889) +- Add High Watermark functionality [#696](https://github.com/Consensys/web3signer/issues/696) + - Update `watermark-repair` subcommand with new options `--set-high-watermark`, `--remove-high-watermark` [#912](https://github.com/Consensys/web3signer/pull/912) + - Add GET `/highWatermark` to eth2 endpoints [#908](https://github.com/Consensys/web3signer/pull/908) ## 23.9.0 diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/WatermarkRepairParameters.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/WatermarkRepairParameters.java index 515f6a02d..179031abf 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/WatermarkRepairParameters.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/WatermarkRepairParameters.java @@ -12,24 +12,30 @@ */ package tech.pegasys.web3signer.dsl.signer; -import java.util.Collections; -import java.util.List; - public class WatermarkRepairParameters { - private final long slot; - private final long epoch; - private final List validators; + private final Long slot; + private final Long epoch; + private final boolean setHighWatermark; + private final boolean removeHighWatermark; public WatermarkRepairParameters(final long slot, final long epoch) { - this(slot, epoch, Collections.emptyList()); + this(slot, epoch, false); } public WatermarkRepairParameters( - final long slot, final long epoch, final List validators) { + final long slot, final long epoch, final boolean setHighWatermark) { this.slot = slot; this.epoch = epoch; - this.validators = validators; + this.setHighWatermark = setHighWatermark; + this.removeHighWatermark = false; + } + + public WatermarkRepairParameters(final boolean removeHighWatermark) { + this.removeHighWatermark = removeHighWatermark; + this.slot = null; + this.epoch = null; + this.setHighWatermark = false; } public long getSlot() { @@ -40,7 +46,11 @@ public long getEpoch() { return epoch; } - public List getValidators() { - return validators; + public boolean isSetHighWatermark() { + return setHighWatermark; + } + + public boolean isRemoveHighWatermark() { + return removeHighWatermark; } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java index b1b753a10..ba9dfc315 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java @@ -271,14 +271,29 @@ private CommandArgs createSubCommandArgs() { params.add("watermark-repair"); // sub-sub command final WatermarkRepairParameters watermarkRepairParameters = signerConfig.getWatermarkRepairParameters().get(); - yamlConfig.append( - String.format( - YAML_NUMERIC_FMT, "eth2.watermark-repair.slot", watermarkRepairParameters.getSlot())); - yamlConfig.append( - String.format( - YAML_NUMERIC_FMT, - "eth2.watermark-repair.epoch", - watermarkRepairParameters.getEpoch())); + if (watermarkRepairParameters.isRemoveHighWatermark()) { + yamlConfig.append( + String.format( + YAML_BOOLEAN_FMT, + "eth2.watermark-repair.remove-high-watermark", + watermarkRepairParameters.isRemoveHighWatermark())); + } else { + yamlConfig.append( + String.format( + YAML_NUMERIC_FMT, + "eth2.watermark-repair.slot", + watermarkRepairParameters.getSlot())); + yamlConfig.append( + String.format( + YAML_NUMERIC_FMT, + "eth2.watermark-repair.epoch", + watermarkRepairParameters.getEpoch())); + yamlConfig.append( + String.format( + YAML_BOOLEAN_FMT, + "eth2.watermark-repair.set-high-watermark", + watermarkRepairParameters.isSetHighWatermark())); + } } return new CommandArgs(params, yamlConfig.toString()); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java index 8444c1cfb..ab9be6f13 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java @@ -442,10 +442,17 @@ private List createSubCommandArgs() { final WatermarkRepairParameters watermarkRepairParameters = signerConfig.getWatermarkRepairParameters().get(); params.add("watermark-repair"); - params.add("--epoch"); - params.add(Long.toString(watermarkRepairParameters.getEpoch())); - params.add("--slot"); - params.add(Long.toString(watermarkRepairParameters.getSlot())); + if (watermarkRepairParameters.isRemoveHighWatermark()) { + params.add("--remove-high-watermark=true"); + } else { + params.add("--epoch"); + params.add(Long.toString(watermarkRepairParameters.getEpoch())); + params.add("--slot"); + params.add(Long.toString(watermarkRepairParameters.getSlot())); + if (watermarkRepairParameters.isSetHighWatermark()) { + params.add("--set-high-watermark=true"); + } + } } return params; diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/WatermarkRepairSubCommandAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/WatermarkRepairSubCommandAcceptanceTest.java index a2278eda3..8185278dc 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/WatermarkRepairSubCommandAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/WatermarkRepairSubCommandAcceptanceTest.java @@ -12,14 +12,20 @@ */ package tech.pegasys.web3signer.tests; +import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.equalTo; import static tech.pegasys.web3signer.dsl.utils.WaitUtils.waitFor; import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.web3signer.BLSTestUtil; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SigningRequestBody; import tech.pegasys.web3signer.dsl.signer.Signer; import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder; import tech.pegasys.web3signer.dsl.signer.WatermarkRepairParameters; +import tech.pegasys.web3signer.dsl.utils.Eth2RequestUtils; import tech.pegasys.web3signer.dsl.utils.MetadataFileHelpers; import tech.pegasys.web3signer.signing.KeyType; @@ -31,8 +37,11 @@ import java.util.Map; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.io.Resources; +import io.restassured.http.ContentType; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.jdbi.v3.core.Jdbi; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -66,20 +75,10 @@ void setupSigner(final Path testDirectory) { void allLowWatermarksAreUpdated(@TempDir final Path testDirectory) throws URISyntaxException { setupSigner(testDirectory); - importSlashingProtectionData(testDirectory); + importSlashingProtectionData(testDirectory, "slashing/slashingImport_two_entries.json"); - final SignerConfigurationBuilder repairBuilder = new SignerConfigurationBuilder(); - repairBuilder.withMode("eth2"); - repairBuilder.withSlashingEnabled(true); - repairBuilder.withSlashingProtectionDbUrl(signer.getSlashingDbUrl()); - repairBuilder.withSlashingProtectionDbUsername("postgres"); - repairBuilder.withSlashingProtectionDbPassword("postgres"); - repairBuilder.withWatermarkRepairParameters(new WatermarkRepairParameters(20000, 30000)); - repairBuilder.withHttpPort(12345); // prevent wait for Ports file in AT - - final Signer watermarkRepairSigner = new Signer(repairBuilder.build(), null); - watermarkRepairSigner.start(); - waitFor(() -> assertThat(watermarkRepairSigner.isRunning()).isFalse()); + final SignerConfigurationBuilder commandConfig = commandConfig(); + executeSubcommand(commandConfig, new WatermarkRepairParameters(20000, 30000)); final Map>> watermarks = getWatermarks(); assertThat(watermarks).hasSize(2); @@ -106,6 +105,140 @@ void allLowWatermarksAreUpdated(@TempDir final Path testDirectory) throws URISyn assertThat(validator2.get("target_epoch")).isEqualTo(epoch); } + @Test + void highWatermarkPreventsImportsAndSignatures(@TempDir final Path testDirectory) + throws URISyntaxException, JsonProcessingException { + + /* + 1. Set high watermark + 2. Importing slashing data beyond high watermark prevents import + 3. Signing beyond high watermark is prevented + 4. Resetting high watermark to lower value fails due to low watermark conflict + 5. Removing high watermark allows slashing import + 6. Can sign beyond previously removed high watermark + */ + + setupSigner(testDirectory); + + // Import validator1's data to set the GVR + importSlashingProtectionData(testDirectory, "slashing/slashingImport.json"); + + final SignerConfigurationBuilder commandConfig = commandConfig(); + + // Set high watermark between the two slashing import entries to prevent second entry from being + // imported + long highWatermarkSlot = 19998L; + long highWatermarkEpoch = 7L; + executeSubcommand( + commandConfig, new WatermarkRepairParameters(highWatermarkSlot, highWatermarkEpoch, true)); + + assertGetHighWatermarkEquals(highWatermarkSlot, highWatermarkEpoch); + + // Import with second entry beyond the high watermark + importSlashingProtectionData(testDirectory, "slashing/slashingImport_two_entries.json"); + + Map>> watermarks = getWatermarks(); + assertThat(watermarks).hasSize(1); + Map validator1 = + watermarks + .get( + "0x8f3f44b74d316c3293cced0c48c72e021ef8d145d136f2908931090e7181c3b777498128a348d07b0b9cd3921b5ca537") + .get(0); + assertThat(validator1.get("slot")).isEqualTo(BigDecimal.valueOf(12345)); + assertThat(validator1.get("source_epoch")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(validator1.get("target_epoch")).isEqualTo(BigDecimal.valueOf(6)); + + // validator2 is not imported due to high watermark + assertThat( + watermarks.get( + "0x98d083489b3b06b8740da2dfec5cc3c01b2086363fe023a9d7dc1f907633b1ff11f7b99b19e0533e969862270061d884")) + .isNull(); + assertThatAllSignaturesAreFrom(validator1); + + // signing beyond high watermark is prevented + Eth2SigningRequestBody blockRequest = + Eth2RequestUtils.createBlockRequest( + UInt64.valueOf(highWatermarkSlot).increment(), Bytes32.fromHexString("0x")); + signer.eth2Sign(keyPair.getPublicKey().toHexString(), blockRequest).then().statusCode(412); + + Eth2SigningRequestBody attestationRequest = + Eth2RequestUtils.createAttestationRequest( + (int) highWatermarkEpoch + 1, + (int) highWatermarkEpoch + 1, + UInt64.valueOf(highWatermarkSlot).decrement()); + signer + .eth2Sign(keyPair.getPublicKey().toHexString(), attestationRequest) + .then() + .statusCode(412); + + // reset high watermark at a lower value fails due to low watermark conflict + executeSubcommand(commandConfig, new WatermarkRepairParameters(12344, 6, true)); + // high watermark is unchanged + assertGetHighWatermarkEquals(highWatermarkSlot, highWatermarkEpoch); + + // remove high watermark allows validator2 through + executeSubcommand(commandConfig, new WatermarkRepairParameters(true)); + + given() + .baseUri(signer.getUrl()) + .get("/api/v1/eth2/highWatermark") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("", anEmptyMap()); + + // With high watermark removed, validator2 is now imported + importSlashingProtectionData(testDirectory, "slashing/slashingImport_two_entries.json"); + + watermarks = getWatermarks(); + assertThat(watermarks).hasSize(2); + + validator1 = + watermarks + .get( + "0x8f3f44b74d316c3293cced0c48c72e021ef8d145d136f2908931090e7181c3b777498128a348d07b0b9cd3921b5ca537") + .get(0); + assertThat(validator1.get("slot")).isEqualTo(BigDecimal.valueOf(12345)); + assertThat(validator1.get("source_epoch")).isEqualTo(BigDecimal.valueOf(5)); + assertThat(validator1.get("target_epoch")).isEqualTo(BigDecimal.valueOf(6)); + + final Map validator2 = + watermarks + .get( + "0x98d083489b3b06b8740da2dfec5cc3c01b2086363fe023a9d7dc1f907633b1ff11f7b99b19e0533e969862270061d884") + .get(0); + assertThat(validator2.get("slot")).isEqualTo(BigDecimal.valueOf(19999)); + assertThat(validator2.get("source_epoch")).isEqualTo(BigDecimal.valueOf(6)); + assertThat(validator2.get("target_epoch")).isEqualTo(BigDecimal.valueOf(7)); + + // signing beyond previously set high watermark is allowed + signer.eth2Sign(keyPair.getPublicKey().toHexString(), blockRequest).then().statusCode(200); + signer + .eth2Sign(keyPair.getPublicKey().toHexString(), attestationRequest) + .then() + .statusCode(200); + } + + private SignerConfigurationBuilder commandConfig() { + final SignerConfigurationBuilder repairBuilder = new SignerConfigurationBuilder(); + repairBuilder.withMode("eth2"); + repairBuilder.withSlashingEnabled(true); + repairBuilder.withSlashingProtectionDbUrl(signer.getSlashingDbUrl()); + repairBuilder.withSlashingProtectionDbUsername("postgres"); + repairBuilder.withSlashingProtectionDbPassword("postgres"); + repairBuilder.withUseConfigFile(true); + repairBuilder.withHttpPort(12345); // prevent wait for Ports file in AT + return repairBuilder; + } + + private void executeSubcommand( + final SignerConfigurationBuilder repairBuilder, final WatermarkRepairParameters params) { + repairBuilder.withWatermarkRepairParameters(params); + final Signer setHighWatermarkSigner = new Signer(repairBuilder.build(), null); + setHighWatermarkSigner.start(); + waitFor(() -> assertThat(setHighWatermarkSigner.isRunning()).isFalse()); + } + private Map>> getWatermarks() { final Jdbi jdbi = Jdbi.create(signer.getSlashingDbUrl(), DB_USERNAME, DB_PASSWORD); return jdbi.withHandle( @@ -120,10 +253,9 @@ private Map>> getWatermarks() { m -> Bytes.wrap((byte[]) m.get("public_key")).toHexString()))); } - private void importSlashingProtectionData(final Path testDirectory) throws URISyntaxException { - final Path importFile = - new File(Resources.getResource("slashing/slashingImport_two_entries.json").toURI()) - .toPath(); + private void importSlashingProtectionData( + final Path testDirectory, final String slashingImportPath) throws URISyntaxException { + final Path importFile = new File(Resources.getResource(slashingImportPath).toURI()).toPath(); final SignerConfigurationBuilder importBuilder = new SignerConfigurationBuilder(); importBuilder.withMode("eth2"); @@ -139,4 +271,37 @@ private void importSlashingProtectionData(final Path testDirectory) throws URISy importSigner.start(); waitFor(() -> assertThat(importSigner.isRunning()).isFalse()); } + + private void assertGetHighWatermarkEquals( + final long highWatermarkSlot, final long highWatermarkEpoch) { + given() + .baseUri(signer.getUrl()) + .get("/api/v1/eth2/highWatermark") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("slot", equalTo(String.valueOf(highWatermarkSlot))) + .body("epoch", equalTo(String.valueOf(highWatermarkEpoch))); + } + + private void assertThatAllSignaturesAreFrom(Map validator1) { + final Jdbi jdbi = Jdbi.create(signer.getSlashingDbUrl(), DB_USERNAME, DB_PASSWORD); + + final List> signedBlocks = + jdbi.withHandle(h -> h.select("SELECT * from signed_blocks").mapToMap().list()); + assertThat(signedBlocks).hasSize(1); + assertThat(signedBlocks.get(0).get("validator_id")).isEqualTo(validator1.get("validator_id")); + assertThat(signedBlocks.get(0).get("slot")) + .isEqualTo(new BigDecimal(validator1.get("slot").toString())); + + final List> signedAttestations = + jdbi.withHandle(h -> h.select("SELECT * from signed_attestations").mapToMap().list()); + assertThat(signedAttestations).hasSize(1); + assertThat(signedAttestations.get(0).get("validator_id")) + .isEqualTo(validator1.get("validator_id")); + assertThat(signedAttestations.get(0).get("source_epoch")) + .isEqualTo(new BigDecimal(validator1.get("source_epoch").toString())); + assertThat(signedAttestations.get(0).get("target_epoch")) + .isEqualTo(new BigDecimal(validator1.get("target_epoch").toString())); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/DeleteKeystoresAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/DeleteKeystoresAcceptanceTest.java index e2b049261..f5bf30f78 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/DeleteKeystoresAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/DeleteKeystoresAcceptanceTest.java @@ -50,12 +50,12 @@ public class DeleteKeystoresAcceptanceTest extends KeyManagerTestBase { + "\"data\" : [ {\n" + " \"pubkey\" : \"0x98d083489b3b06b8740da2dfec5cc3c01b2086363fe023a9d7dc1f907633b1ff11f7b99b19e0533e969862270061d884\",\n" + " \"signed_blocks\" : [ {\n" - + " \"slot\" : \"12345\",\n" + + " \"slot\" : \"19999\",\n" + " \"signing_root\" : \"0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b\"\n" + " } ],\n" + " \"signed_attestations\" : [ {\n" - + " \"source_epoch\" : \"5\",\n" - + " \"target_epoch\" : \"6\",\n" + + " \"source_epoch\" : \"6\",\n" + + " \"target_epoch\" : \"7\",\n" + " \"signing_root\" : \"0x30752da173420e64a66f6ca6b97c55a96390a3158a755ecd277812488bb84e57\"\n" + " } ]\n" + "} ]\n" diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/ImportKeystoresAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/ImportKeystoresAcceptanceTest.java index 1becea8d5..a7ad8ede1 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/ImportKeystoresAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/keymanager/ImportKeystoresAcceptanceTest.java @@ -164,7 +164,7 @@ public void canSignAfterImportingNewKey() throws IOException, URISyntaxException .statusCode(200) .body("data.status", hasItem("imported")); // Sign with it - final Eth2SigningRequestBody request = createAttestationRequest(5, 6, UInt64.ZERO); + final Eth2SigningRequestBody request = createAttestationRequest(7, 8, UInt64.ZERO); signer.eth2Sign(PUBLIC_KEY, request).then().assertThat().statusCode(200); } @@ -194,7 +194,7 @@ public void canSignPreviouslyDeletedKeyAfterImporting() throws IOException, URIS validateApiResponse(callListKeys(), "data.validating_pubkey", hasItem(PUBLIC_KEY)); // Sign with it - final Eth2SigningRequestBody request = createAttestationRequest(5, 6, UInt64.ZERO); + final Eth2SigningRequestBody request = createAttestationRequest(7, 8, UInt64.ZERO); signer.eth2Sign(PUBLIC_KEY, request).then().assertThat().statusCode(200); } diff --git a/acceptance-tests/src/test/resources/slashing/slashingImport_two_entries.json b/acceptance-tests/src/test/resources/slashing/slashingImport_two_entries.json index 9ba847ba8..fb0f70c11 100644 --- a/acceptance-tests/src/test/resources/slashing/slashingImport_two_entries.json +++ b/acceptance-tests/src/test/resources/slashing/slashingImport_two_entries.json @@ -24,14 +24,14 @@ "pubkey": "0x98d083489b3b06b8740da2dfec5cc3c01b2086363fe023a9d7dc1f907633b1ff11f7b99b19e0533e969862270061d884", "signed_blocks": [ { - "slot": "12345", + "slot": "19999", "signing_root": "0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b" } ], "signed_attestations": [ { - "source_epoch": "5", - "target_epoch": "6", + "source_epoch": "6", + "target_epoch": "7", "signing_root": "0x30752da173420e64a66f6ca6b97c55a96390a3158a755ecd277812488bb84e57" } ] diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2WatermarkRepairSubCommand.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2WatermarkRepairSubCommand.java index 335fcf7d7..5462c0283 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2WatermarkRepairSubCommand.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2WatermarkRepairSubCommand.java @@ -14,7 +14,9 @@ import tech.pegasys.web3signer.slashingprotection.SlashingProtectionContext; import tech.pegasys.web3signer.slashingprotection.SlashingProtectionContextFactory; +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.Validator; import tech.pegasys.web3signer.slashingprotection.dao.ValidatorsDao; @@ -24,6 +26,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt64; +import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import picocli.CommandLine; import picocli.CommandLine.Command; @@ -33,8 +36,9 @@ @Command( name = "watermark-repair", description = - "Updates the slashing protection low watermark for validators. " - + "This will not move the low watermark lower, the watermark can only be increased.", + "Updates the slashing protection low watermark or high watermark for all validators. " + + "This will not move the low watermark lower, the low watermark can only be increased." + + "If setting the high watermark, care should be taken to set this to a future epoch and slot.", subcommands = {HelpCommand.class}, mixinStandardHelpOptions = true) public class Eth2WatermarkRepairSubCommand implements Runnable { @@ -45,40 +49,75 @@ public class Eth2WatermarkRepairSubCommand implements Runnable { @Option( names = {"--epoch"}, paramLabel = "", - description = "Low watermark to set the attestation source and target to.", + description = + "Low watermark to set the attestation source and target to. (Sets high watermark epoch when --set-high-watermark=true).", arity = "1") Long epoch; @Option( names = "--slot", - paramLabel = "", - description = "Low watermark to set the block slot to.", + paramLabel = "", + description = + "Low watermark to set the block slot to. (Sets high watermark slot when --set-high-watermark=true).", arity = "1") Long slot; + @Option( + names = "--set-high-watermark", + paramLabel = "", + description = + "Sets high watermark to given epoch and slot. (Sets low watermark when --set-high-watermark=false)." + + " (Default: ${DEFAULT-VALUE})") + boolean setHighWatermark = false; + + @Option( + names = "--remove-high-watermark", + paramLabel = "", + description = + "Removes high watermark. When set to true, all other subcommand options are ignored." + + " (Default: ${DEFAULT-VALUE})") + boolean removeHighWatermark = false; + @Override public void run() { final LowWatermarkDao lowWatermarkDao = new LowWatermarkDao(); + final MetadataDao metadataDao = new MetadataDao(); final ValidatorsDao validatorsDao = new ValidatorsDao(); final SlashingProtectionContext slashingProtectionContext = SlashingProtectionContextFactory.create(eth2Config.getSlashingProtectionParameters()); final Jdbi jdbi = slashingProtectionContext.getSlashingProtectionJdbi(); - final List allValidators = - jdbi.inTransaction(h -> validatorsDao.findAllValidators(h).collect(Collectors.toList())); - - allValidators.stream() - .parallel() - .forEach( - validator -> - jdbi.useTransaction( - h -> { - lowWatermarkDao.updateSlotWatermarkFor( - h, validator.getId(), UInt64.valueOf(slot)); - lowWatermarkDao.updateEpochWatermarksFor( - h, validator.getId(), UInt64.valueOf(epoch), UInt64.valueOf(epoch)); - })); - LOG.info("Updated low watermark for {} validators", allValidators.size()); + if (removeHighWatermark) { + jdbi.useTransaction(metadataDao::deleteHighWatermark); + LOG.info("Removed high watermark for all validators"); + } else if (setHighWatermark) { + jdbi.useTransaction(h -> setHighWatermark(h, metadataDao)); + LOG.info("Updated high watermark for all validators"); + } else { + final List allValidators = + jdbi.inTransaction(h -> validatorsDao.findAllValidators(h).collect(Collectors.toList())); + + LOG.info("Updating low watermark for all {} validators...", allValidators.size()); + + allValidators.stream() + .parallel() + .forEach( + validator -> + jdbi.useTransaction(h -> setLowWatermark(h, validator, lowWatermarkDao))); + + LOG.info("Updated low watermark for all {} validators", allValidators.size()); + } + } + + private void setHighWatermark(Handle h, MetadataDao metadataDao) { + metadataDao.updateHighWatermark( + h, new HighWatermark(UInt64.valueOf(slot), UInt64.valueOf(epoch))); + } + + private void setLowWatermark(Handle h, Validator validator, LowWatermarkDao lowWatermarkDao) { + lowWatermarkDao.updateSlotWatermarkFor(h, validator.getId(), UInt64.valueOf(slot)); + lowWatermarkDao.updateEpochWatermarksFor( + h, validator.getId(), UInt64.valueOf(epoch), UInt64.valueOf(epoch)); } }