From fad8dcf7aabd845d05f9561e2669c99b2a538ba9 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Wed, 10 Apr 2024 17:24:12 +0100 Subject: [PATCH 01/12] Add support for slashing interchange format tests --- build.gradle | 34 ++++++- eth-reference-tests/build.gradle | 3 + .../teku/reference/Eth2ReferenceTestCase.java | 2 + .../pegasys/teku/reference/TestDataUtils.java | 16 ++++ ...hingProtectionInterchangeTestExecutor.java | 93 +++++++++++++++++++ .../ethtests/finder/BlsRefTestFinder.java | 2 +- .../ethtests/finder/ReferenceTestFinder.java | 6 +- ...ashingProtectionInterchangeTestFinder.java | 42 +++++++++ 8 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java create mode 100644 eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java diff --git a/build.gradle b/build.gradle index 5124105b7f4..cc70c059324 100644 --- a/build.gradle +++ b/build.gradle @@ -297,10 +297,13 @@ allprojects { def refTestVersion = 'v1.4.0' // Arbitrary change to refresh cache number: 1 def blsRefTestVersion = 'v0.1.2' +def slashingProtectionRefTestVersion = 'v5.3.0' def refTestBaseUrl = 'https://github.com/ethereum/consensus-spec-tests/releases/download' def blsRefTestBaseUrl = 'https://github.com/ethereum/bls12-381-tests/releases/download' +def slashingProtectionRefTestBaseUrl = 'https://github.com/eth-clients/slashing-protection-interchange-tests/archive/refs/tags' def refTestDownloadDir = "${buildDir}/refTests/${refTestVersion}" def blsRefTestDownloadDir = "${buildDir}/blsRefTests/${blsRefTestVersion}" +def slashingProtectionRefTestDownloadDir = "${buildDir}/slashingProtectionRefTests/${slashingProtectionRefTestVersion}" def refTestExpandDir = "${project.rootDir}/eth-reference-tests/src/referenceTest/resources/consensus-spec-tests/" task downloadEthRefTests(type: Download) { @@ -321,7 +324,15 @@ task downloadBlsRefTests(type: Download) { overwrite false } -task downloadRefTests(dependsOn: [downloadEthRefTests, downloadBlsRefTests]) +task downloadSlashingProtectionRefTests(type: Download) { + src([ + "${slashingProtectionRefTestBaseUrl}/${slashingProtectionRefTestVersion}.tar.gz" + ]) + dest "${slashingProtectionRefTestDownloadDir}/slashing-protection-interchange-tests.tar.gz" + overwrite false +} + +task downloadRefTests(dependsOn: [downloadEthRefTests, downloadBlsRefTests, downloadSlashingProtectionRefTests]) task cleanRefTestsGeneral(type: Delete) { delete "${refTestExpandDir}/tests/general" @@ -359,8 +370,25 @@ task expandRefTestsBls(type: Copy, dependsOn: [cleanRefTestsBls, downloadBlsRefT into "${refTestExpandDir}/tests/bls" } -task expandRefTests(dependsOn: [expandRefTestsGeneral, expandRefTestsMainnet, expandRefTestsMinimal, expandRefTestsBls]) -task cleanRefTests(dependsOn: [cleanRefTestsGeneral, cleanRefTestsMainnet, cleanRefTestsMinimal, cleanRefTestsBls]) +task cleanRefTestsSlashingProtection(type: Delete) { + delete "${refTestExpandDir}/tests/slashing-protection-interchange" +} + +task expandRefTestsSlashingProtection(type: Copy, dependsOn: [cleanRefTestsSlashingProtection, downloadSlashingProtectionRefTests]) { + into "${refTestExpandDir}/tests/slashing-protection-interchange" + from { + tarTree("${slashingProtectionRefTestDownloadDir}/slashing-protection-interchange-tests.tar.gz").matching { + include "**/tests/generated/*.json" + // flatten the directory structure + eachFile { FileCopyDetails fcp -> + fcp.path = fcp.name + } + } + } +} + +task expandRefTests(dependsOn: [expandRefTestsGeneral, expandRefTestsMainnet, expandRefTestsMinimal, expandRefTestsBls, expandRefTestsSlashingProtection]) +task cleanRefTests(dependsOn: [cleanRefTestsGeneral, cleanRefTestsMainnet, cleanRefTestsMinimal, cleanRefTestsBls, cleanRefTestsSlashingProtection]) task deploy() {} diff --git a/eth-reference-tests/build.gradle b/eth-reference-tests/build.gradle index 17954ada5fe..65b4fb739d1 100644 --- a/eth-reference-tests/build.gradle +++ b/eth-reference-tests/build.gradle @@ -15,9 +15,12 @@ dependencies { referenceTestImplementation project(':storage') referenceTestImplementation testFixtures(project(':storage')) referenceTestImplementation project(':infrastructure:async') + referenceTestImplementation project(':infrastructure:io') referenceTestImplementation testFixtures(project(':infrastructure:async')) referenceTestImplementation testFixtures(project(':infrastructure:metrics')) referenceTestImplementation project(':infrastructure:time') + referenceTestImplementation project(':data:dataexchange') + referenceTestImplementation project(':data:serializer') referenceTestImplementation 'org.hyperledger.besu:plugin-api' referenceTestImplementation 'com.fasterxml.jackson.core:jackson-databind' diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java index 2be8f4414ea..ca669b4a0d3 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java @@ -31,6 +31,7 @@ import tech.pegasys.teku.reference.phase0.rewards.RewardsTestExecutorPhase0; import tech.pegasys.teku.reference.phase0.sanity.SanityTests; import tech.pegasys.teku.reference.phase0.shuffling.ShufflingTestExecutor; +import tech.pegasys.teku.reference.phase0.slashing_protection_interchange.SlashingProtectionInterchangeTestExecutor; import tech.pegasys.teku.reference.phase0.ssz_generic.SszGenericTests; import tech.pegasys.teku.reference.phase0.ssz_static.SszTestExecutor; @@ -48,6 +49,7 @@ public abstract class Eth2ReferenceTestCase { .putAll(SszGenericTests.SSZ_GENERIC_TEST_TYPES) .putAll(OperationsTestExecutor.OPERATIONS_TEST_TYPES) .putAll(SanityTests.SANITY_TEST_TYPES) + .put("slashing-protection-interchange", new SlashingProtectionInterchangeTestExecutor()) .put("light_client/single_merkle_proof", TestExecutor.IGNORE_TESTS) .put("light_client/sync", TestExecutor.IGNORE_TESTS) .put("light_client/update_ranking", TestExecutor.IGNORE_TESTS) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java index ddedd55c3af..bd696597ee1 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java @@ -13,6 +13,8 @@ package tech.pegasys.teku.reference; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLParser; @@ -29,11 +31,13 @@ import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; import tech.pegasys.teku.infrastructure.ssz.SszData; import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; +import tech.pegasys.teku.provider.JsonProvider; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; public class TestDataUtils { private static final YAMLFactory YAML_FACTORY; + private static final JsonProvider JSON_PROVIDER; static { final LoaderOptions loaderOptions = new LoaderOptions(); @@ -41,6 +45,7 @@ public class TestDataUtils { // https://github.com/FasterXML/jackson-dataformats-text/tree/2.15/yaml#maximum-input-yaml-document-size-3-mb loaderOptions.setCodePointLimit(1024 * 1024 * 100); YAML_FACTORY = YAMLFactory.builder().loaderOptions(loaderOptions).build(); + JSON_PROVIDER = new JsonProvider(); } public static T loadSsz( @@ -100,4 +105,15 @@ public static T loadYaml( return type.deserialize(in); } } + + public static JsonNode loadJson(final TestDefinition testDefinition, final String fileName) + throws IOException { + final Path path = testDefinition.getTestDirectory().resolve(fileName); + return JSON_PROVIDER.getObjectMapper().readTree(Files.newInputStream(path)); + } + + public static T jsonToObject(final String json, final Class type) + throws JsonProcessingException { + return JSON_PROVIDER.jsonToObject(json, type); + } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java new file mode 100644 index 00000000000..af2c875c878 --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -0,0 +1,93 @@ +/* + * 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.reference.phase0.slashing_protection_interchange; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.JsonNode; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.data.SlashingProtectionImporter; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.infrastructure.io.SyncDataAccessor; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.reference.TestDataUtils; +import tech.pegasys.teku.reference.TestExecutor; +import tech.pegasys.teku.spec.signatures.LocalSlashingProtector; + +public class SlashingProtectionInterchangeTestExecutor implements TestExecutor { + + private static final Logger LOG = LogManager.getLogger(); + + //TODO: implement the logic + @Override + public void runTest(final TestDefinition testDefinition) throws Throwable { + final JsonNode testNode = TestDataUtils.loadJson(testDefinition, testDefinition.getTestName()); + + final String testName = testNode.get("name").asText(); + final Bytes32 genesisValidatorsRoot = + Bytes32.fromHexString(testNode.get("genesis_validators_root").asText()); + + LOG.info("Running {}", testName); + + final Path tempDir = Files.createTempDirectory(testName); + + final Path slashProtectionPath = tempDir.resolve("slashprotection"); + + final Path slashProtectionImportFile = tempDir.resolve("import.yml"); + + Files.writeString( + slashProtectionImportFile, + testNode.get("steps").get(0).get("interchange").toString(), + StandardCharsets.UTF_8); + + final BLSPublicKey pubkey = + BLSPublicKey.fromHexString( + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"); + + Files.createDirectories(slashProtectionPath); + SlashingProtectionImporter importer = new SlashingProtectionImporter(slashProtectionPath); + importer.initialise(slashProtectionImportFile.toFile()); + final Map errors = importer.updateLocalRecords((__) -> {}); + + assertThat(errors).isEmpty(); + + LocalSlashingProtector localSlashingProtector = + new LocalSlashingProtector( + SyncDataAccessor.create(slashProtectionPath), slashProtectionPath); + assertThat( + localSlashingProtector.maySignBlock(pubkey, genesisValidatorsRoot, UInt64.valueOf(10))) + .isCompletedWithValue(false); + assertThat( + localSlashingProtector.maySignBlock(pubkey, genesisValidatorsRoot, UInt64.valueOf(13))) + .isCompletedWithValue(false); + assertThat( + localSlashingProtector.maySignBlock(pubkey, genesisValidatorsRoot, UInt64.valueOf(14))) + .isCompletedWithValue(true); + assertThat( + localSlashingProtector.maySignAttestation( + pubkey, genesisValidatorsRoot, UInt64.valueOf(0), UInt64.valueOf(2))) + .isCompletedWithValue(false); + assertThat( + localSlashingProtector.maySignAttestation( + pubkey, genesisValidatorsRoot, UInt64.valueOf(1), UInt64.valueOf(3))) + .isCompletedWithValue(false); + } +} diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java index 14ede1f211d..414fde73f21 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java @@ -39,7 +39,7 @@ public Stream findTests(final String fork, final String spec, fi @MustBeClosed private Stream findBlsTests( final String spec, final Path testRoot, final Path testCategoryDir) throws IOException { - final String testType = "bls/" + testRoot.relativize(testCategoryDir).toString(); + final String testType = "bls/" + testRoot.relativize(testCategoryDir); return Files.list(testCategoryDir) .filter(file -> file.toFile().getName().endsWith(".yaml")) .map( diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java index 656471c79c4..66b6905431c 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java @@ -47,7 +47,10 @@ public static Stream findReferenceTests() throws IOException { private static Stream findTestTypes(final Path specDirectory) throws IOException { final String spec = specDirectory.getFileName().toString(); if (spec.equals("bls")) { - return new BlsRefTestFinder().findTests(TestFork.PHASE0, spec, specDirectory); + return new BlsRefTestFinder().findTests("", spec, specDirectory); + } + if (spec.equals("slashing-protection-interchange")) { + return new SlashingProtectionInterchangeTestFinder().findTests("", spec, specDirectory); } return SUPPORTED_FORKS.stream() .flatMap( @@ -60,7 +63,6 @@ private static Stream findTestTypes(final Path specDirectory) th return Stream.of( new BlsTestFinder(), new KzgTestFinder(), - new BlsRefTestFinder(), new SszTestFinder("ssz_generic"), new SszTestFinder("ssz_static"), new ShufflingTestFinder(), diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java new file mode 100644 index 00000000000..5d5e8e8345b --- /dev/null +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java @@ -0,0 +1,42 @@ +/* + * 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.ethtests.finder; + +import com.google.errorprone.annotations.MustBeClosed; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class SlashingProtectionInterchangeTestFinder implements TestFinder { + + @Override + @MustBeClosed + public Stream findTests(final String fork, final String spec, final Path testRoot) + throws IOException { + if (!spec.equals("slashing-protection-interchange")) { + return Stream.empty(); + } + return Files.list(testRoot) + .filter(file -> file.toFile().getName().endsWith(".json")) + .map( + testFile -> + new TestDefinition( + fork, + spec, + spec, + testFile.toFile().getName(), + testRoot.relativize(testFile.getParent()))); + } +} From a7850ff59a9bdaf1f3a3324e059844b4f49d7cab Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Wed, 10 Apr 2024 17:31:12 +0100 Subject: [PATCH 02/12] fix spotless --- .../SlashingProtectionInterchangeTestExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index af2c875c878..dab6df4ee36 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -36,7 +36,7 @@ public class SlashingProtectionInterchangeTestExecutor implements TestExecutor { private static final Logger LOG = LogManager.getLogger(); - //TODO: implement the logic + // TODO: implement the logic @Override public void runTest(final TestDefinition testDefinition) throws Throwable { final JsonNode testNode = TestDataUtils.loadJson(testDefinition, testDefinition.getTestName()); From cfca4064a56a3f49613528527fe4a24451b39bbe Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Thu, 11 Apr 2024 12:30:15 +0100 Subject: [PATCH 03/12] implement logic for testing --- build.gradle | 30 ++-- .../pegasys/teku/reference/TestDataUtils.java | 18 +- ...hingProtectionInterchangeTestExecutor.java | 166 ++++++++++++------ .../ethtests/finder/BlsRefTestFinder.java | 12 +- .../ethtests/finder/ReferenceTestFinder.java | 2 +- ...ngProtectionInterchangeRefTestFinder.java} | 12 +- 6 files changed, 151 insertions(+), 89 deletions(-) rename eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/{SlashingProtectionInterchangeTestFinder.java => SlashingProtectionInterchangeRefTestFinder.java} (78%) diff --git a/build.gradle b/build.gradle index cc70c059324..9b91c74e362 100644 --- a/build.gradle +++ b/build.gradle @@ -297,13 +297,13 @@ allprojects { def refTestVersion = 'v1.4.0' // Arbitrary change to refresh cache number: 1 def blsRefTestVersion = 'v0.1.2' -def slashingProtectionRefTestVersion = 'v5.3.0' +def slashingProtectionInterchangeRefTestVersion = 'v5.3.0' def refTestBaseUrl = 'https://github.com/ethereum/consensus-spec-tests/releases/download' def blsRefTestBaseUrl = 'https://github.com/ethereum/bls12-381-tests/releases/download' -def slashingProtectionRefTestBaseUrl = 'https://github.com/eth-clients/slashing-protection-interchange-tests/archive/refs/tags' +def slashingProtectionInterchangeRefTestBaseUrl = 'https://github.com/eth-clients/slashing-protection-interchange-tests/archive/refs/tags' def refTestDownloadDir = "${buildDir}/refTests/${refTestVersion}" def blsRefTestDownloadDir = "${buildDir}/blsRefTests/${blsRefTestVersion}" -def slashingProtectionRefTestDownloadDir = "${buildDir}/slashingProtectionRefTests/${slashingProtectionRefTestVersion}" +def slashingProtectionInterchangeRefTestDownloadDir = "${buildDir}/slashingProtectionInterchangeRefTests/${slashingProtectionInterchangeRefTestVersion}" def refTestExpandDir = "${project.rootDir}/eth-reference-tests/src/referenceTest/resources/consensus-spec-tests/" task downloadEthRefTests(type: Download) { @@ -324,15 +324,15 @@ task downloadBlsRefTests(type: Download) { overwrite false } -task downloadSlashingProtectionRefTests(type: Download) { +task downloadSlashingProtectionInterchangeRefTests(type: Download) { src([ - "${slashingProtectionRefTestBaseUrl}/${slashingProtectionRefTestVersion}.tar.gz" + "${slashingProtectionInterchangeRefTestBaseUrl}/${slashingProtectionInterchangeRefTestVersion}.tar.gz" ]) - dest "${slashingProtectionRefTestDownloadDir}/slashing-protection-interchange-tests.tar.gz" + dest "${slashingProtectionInterchangeRefTestDownloadDir}/slashing-protection-interchange-tests.tar.gz" overwrite false } -task downloadRefTests(dependsOn: [downloadEthRefTests, downloadBlsRefTests, downloadSlashingProtectionRefTests]) +task downloadRefTests(dependsOn: [downloadEthRefTests, downloadBlsRefTests, downloadSlashingProtectionInterchangeRefTests]) task cleanRefTestsGeneral(type: Delete) { delete "${refTestExpandDir}/tests/general" @@ -370,25 +370,25 @@ task expandRefTestsBls(type: Copy, dependsOn: [cleanRefTestsBls, downloadBlsRefT into "${refTestExpandDir}/tests/bls" } -task cleanRefTestsSlashingProtection(type: Delete) { +task cleanRefTestsSlashingProtectionInterchange(type: Delete) { delete "${refTestExpandDir}/tests/slashing-protection-interchange" } -task expandRefTestsSlashingProtection(type: Copy, dependsOn: [cleanRefTestsSlashingProtection, downloadSlashingProtectionRefTests]) { - into "${refTestExpandDir}/tests/slashing-protection-interchange" +task expandRefTestsSlashingProtectionInterchange(type: Copy, dependsOn: [cleanRefTestsSlashingProtectionInterchange, downloadSlashingProtectionInterchangeRefTests]) { from { - tarTree("${slashingProtectionRefTestDownloadDir}/slashing-protection-interchange-tests.tar.gz").matching { + tarTree("${slashingProtectionInterchangeRefTestDownloadDir}/slashing-protection-interchange-tests.tar.gz").matching { include "**/tests/generated/*.json" - // flatten the directory structure + // flatten eachFile { FileCopyDetails fcp -> - fcp.path = fcp.name + fcp.path = fcp.name } } } + into "${refTestExpandDir}/tests/slashing-protection-interchange" } -task expandRefTests(dependsOn: [expandRefTestsGeneral, expandRefTestsMainnet, expandRefTestsMinimal, expandRefTestsBls, expandRefTestsSlashingProtection]) -task cleanRefTests(dependsOn: [cleanRefTestsGeneral, cleanRefTestsMainnet, cleanRefTestsMinimal, cleanRefTestsBls, cleanRefTestsSlashingProtection]) +task expandRefTests(dependsOn: [expandRefTestsGeneral, expandRefTestsMainnet, expandRefTestsMinimal, expandRefTestsBls, expandRefTestsSlashingProtectionInterchange]) +task cleanRefTests(dependsOn: [cleanRefTestsGeneral, cleanRefTestsMainnet, cleanRefTestsMinimal, cleanRefTestsBls, cleanRefTestsSlashingProtectionInterchange]) task deploy() {} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java index bd696597ee1..7e2039767ae 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/TestDataUtils.java @@ -13,14 +13,13 @@ package tech.pegasys.teku.reference; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLParser; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Function; @@ -90,7 +89,7 @@ public static T loadYaml( throws IOException { final Path path = testDefinition.getTestDirectory().resolve(fileName); try (final InputStream in = Files.newInputStream(path)) { - return new ObjectMapper(YAML_FACTORY).readerFor(type).readValue(in); + return new ObjectMapper(YAML_FACTORY).readValue(in, type); } } @@ -106,14 +105,17 @@ public static T loadYaml( } } - public static JsonNode loadJson(final TestDefinition testDefinition, final String fileName) + public static T loadJson( + final TestDefinition testDefinition, final String fileName, final Class type) throws IOException { final Path path = testDefinition.getTestDirectory().resolve(fileName); - return JSON_PROVIDER.getObjectMapper().readTree(Files.newInputStream(path)); + try (final InputStream in = Files.newInputStream(path)) { + return JSON_PROVIDER.getObjectMapper().readValue(in, type); + } } - public static T jsonToObject(final String json, final Class type) - throws JsonProcessingException { - return JSON_PROVIDER.jsonToObject(json, type); + public static void writeJsonToFile(final T object, final Path file) throws IOException { + final String json = JSON_PROVIDER.getObjectMapper().writeValueAsString(object); + Files.writeString(file, json, StandardCharsets.UTF_8); } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index dab6df4ee36..75641bc1b62 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -15,79 +15,139 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.fasterxml.jackson.databind.JsonNode; -import java.nio.charset.StandardCharsets; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.data.SlashingProtectionImporter; +import tech.pegasys.teku.data.slashinginterchange.SlashingProtectionInterchangeFormat; import tech.pegasys.teku.ethtests.finder.TestDefinition; import tech.pegasys.teku.infrastructure.io.SyncDataAccessor; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.reference.TestDataUtils; import tech.pegasys.teku.reference.TestExecutor; +import tech.pegasys.teku.reference.phase0.slashing_protection_interchange.SlashingProtectionInterchangeTestExecutor.TestData.Step; import tech.pegasys.teku.spec.signatures.LocalSlashingProtector; public class SlashingProtectionInterchangeTestExecutor implements TestExecutor { private static final Logger LOG = LogManager.getLogger(); - // TODO: implement the logic @Override public void runTest(final TestDefinition testDefinition) throws Throwable { - final JsonNode testNode = TestDataUtils.loadJson(testDefinition, testDefinition.getTestName()); - - final String testName = testNode.get("name").asText(); - final Bytes32 genesisValidatorsRoot = - Bytes32.fromHexString(testNode.get("genesis_validators_root").asText()); - - LOG.info("Running {}", testName); - - final Path tempDir = Files.createTempDirectory(testName); - - final Path slashProtectionPath = tempDir.resolve("slashprotection"); - - final Path slashProtectionImportFile = tempDir.resolve("import.yml"); - - Files.writeString( - slashProtectionImportFile, - testNode.get("steps").get(0).get("interchange").toString(), - StandardCharsets.UTF_8); - - final BLSPublicKey pubkey = - BLSPublicKey.fromHexString( - "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"); - - Files.createDirectories(slashProtectionPath); - SlashingProtectionImporter importer = new SlashingProtectionImporter(slashProtectionPath); - importer.initialise(slashProtectionImportFile.toFile()); - final Map errors = importer.updateLocalRecords((__) -> {}); - - assertThat(errors).isEmpty(); - - LocalSlashingProtector localSlashingProtector = - new LocalSlashingProtector( - SyncDataAccessor.create(slashProtectionPath), slashProtectionPath); - assertThat( - localSlashingProtector.maySignBlock(pubkey, genesisValidatorsRoot, UInt64.valueOf(10))) - .isCompletedWithValue(false); - assertThat( - localSlashingProtector.maySignBlock(pubkey, genesisValidatorsRoot, UInt64.valueOf(13))) - .isCompletedWithValue(false); - assertThat( - localSlashingProtector.maySignBlock(pubkey, genesisValidatorsRoot, UInt64.valueOf(14))) - .isCompletedWithValue(true); - assertThat( - localSlashingProtector.maySignAttestation( - pubkey, genesisValidatorsRoot, UInt64.valueOf(0), UInt64.valueOf(2))) - .isCompletedWithValue(false); - assertThat( - localSlashingProtector.maySignAttestation( - pubkey, genesisValidatorsRoot, UInt64.valueOf(1), UInt64.valueOf(3))) - .isCompletedWithValue(false); + final TestData testData = + TestDataUtils.loadJson(testDefinition, testDefinition.getTestName(), TestData.class); + + // our implementation does not fail importing when the GVR in the interchange mismatches the one + // from the spec + if (testData.name.startsWith("wrong_genesis_validators_root")) { + LOG.info("Not going to run {}", testData.name); + return; + } + + LOG.info("Running {}", testData.name); + + final Path tempDir = Files.createTempDirectory(testData.name); + try { + runTest(testData, tempDir); + } finally { + deleteDirectory(tempDir); + } + } + + private void runTest(final TestData testData, final Path tempDir) { + final SlashingProtectionImporter importer = new SlashingProtectionImporter(tempDir); + final LocalSlashingProtector slashingProtector = + new LocalSlashingProtector(SyncDataAccessor.create(tempDir), tempDir); + testData.steps.forEach( + step -> + runStep(step, testData.genesisValidatorsRoot, importer, slashingProtector, tempDir)); + } + + private void runStep( + final Step step, + final Bytes32 genesisValidatorsRoot, + final SlashingProtectionImporter importer, + final LocalSlashingProtector slashingProtector, + final Path tempDir) { + final Path importFile = tempDir.resolve("import.yml"); + try { + TestDataUtils.writeJsonToFile(step.interchange, importFile); + importer.initialise(importFile.toFile()); + // cleanup + Files.delete(importFile); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + final Map importErrors = + importer.updateLocalRecords(status -> LOG.info("Import status: " + status)); + if (step.shouldSucceed) { + assertThat(importErrors).isEmpty(); + } else { + assertThat(importErrors).isNotEmpty(); + } + step.blocks.forEach( + block -> + assertThat( + slashingProtector.maySignBlock(block.pubkey, genesisValidatorsRoot, block.slot)) + .isCompletedWithValue(block.shouldSucceed)); + step.attestations.forEach( + attestation -> + assertThat( + slashingProtector.maySignAttestation( + attestation.pubkey, + genesisValidatorsRoot, + attestation.sourceEpoch, + attestation.targetEpoch)) + .isCompletedWithValue(attestation.shouldSucceed)); + } + + private void deleteDirectory(final Path dir) { + try (DirectoryStream files = Files.newDirectoryStream(dir)) { + for (Path file : files) { + if (Files.isRegularFile(file)) { + Files.delete(file); + } + } + Files.delete(dir); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + public record TestData( + String name, + @JsonProperty("genesis_validators_root") Bytes32 genesisValidatorsRoot, + List steps) { + + public record Step( + @JsonProperty("should_succeed") boolean shouldSucceed, + @JsonProperty("contains_slashable_data") boolean containsSlashableData, + SlashingProtectionInterchangeFormat interchange, + List blocks, + List attestations) {} + + public record Block( + BLSPublicKey pubkey, + UInt64 slot, + @JsonProperty("signing_root") Bytes32 signingRoot, + @JsonProperty("should_succeed") boolean shouldSucceed, + @JsonProperty("should_succeed_complete") boolean shouldSucceedComplete) {} + + public record Attestation( + BLSPublicKey pubkey, + @JsonProperty("source_epoch") UInt64 sourceEpoch, + @JsonProperty("target_epoch") UInt64 targetEpoch, + @JsonProperty("signing_root") Bytes32 signingRoot, + @JsonProperty("should_succeed") boolean shouldSucceed, + @JsonProperty("should_succeed_complete") boolean shouldSucceedComplete) {} } } diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java index 414fde73f21..a67f28e2af7 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/BlsRefTestFinder.java @@ -26,19 +26,19 @@ public class BlsRefTestFinder implements TestFinder { @Override @MustBeClosed - public Stream findTests(final String fork, final String spec, final Path testRoot) - throws IOException { - if (!spec.equals("bls")) { + public Stream findTests( + final String fork, final String config, final Path testRoot) throws IOException { + if (!config.equals("bls")) { return Stream.empty(); } return Files.list(testRoot) .filter(path -> path.toFile().isDirectory()) - .flatMap(unchecked(path -> findBlsTests(spec, testRoot, path))); + .flatMap(unchecked(path -> findBlsTests(config, testRoot, path))); } @MustBeClosed private Stream findBlsTests( - final String spec, final Path testRoot, final Path testCategoryDir) throws IOException { + final String config, final Path testRoot, final Path testCategoryDir) throws IOException { final String testType = "bls/" + testRoot.relativize(testCategoryDir); return Files.list(testCategoryDir) .filter(file -> file.toFile().getName().endsWith(".yaml")) @@ -46,7 +46,7 @@ private Stream findBlsTests( testFile -> new TestDefinition( "", - spec, + config, testType, testFile.toFile().getName(), testRoot.relativize(testCategoryDir))); diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java index 66b6905431c..d58be7186fa 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java @@ -50,7 +50,7 @@ private static Stream findTestTypes(final Path specDirectory) th return new BlsRefTestFinder().findTests("", spec, specDirectory); } if (spec.equals("slashing-protection-interchange")) { - return new SlashingProtectionInterchangeTestFinder().findTests("", spec, specDirectory); + return new SlashingProtectionInterchangeRefTestFinder().findTests("", spec, specDirectory); } return SUPPORTED_FORKS.stream() .flatMap( diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeRefTestFinder.java similarity index 78% rename from eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java rename to eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeRefTestFinder.java index 5d5e8e8345b..6470bb4ea3f 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeTestFinder.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/SlashingProtectionInterchangeRefTestFinder.java @@ -19,13 +19,13 @@ import java.nio.file.Path; import java.util.stream.Stream; -public class SlashingProtectionInterchangeTestFinder implements TestFinder { +public class SlashingProtectionInterchangeRefTestFinder implements TestFinder { @Override @MustBeClosed - public Stream findTests(final String fork, final String spec, final Path testRoot) - throws IOException { - if (!spec.equals("slashing-protection-interchange")) { + public Stream findTests( + final String fork, final String config, final Path testRoot) throws IOException { + if (!config.equals("slashing-protection-interchange")) { return Stream.empty(); } return Files.list(testRoot) @@ -34,8 +34,8 @@ public Stream findTests(final String fork, final String spec, fi testFile -> new TestDefinition( fork, - spec, - spec, + config, + config, testFile.toFile().getName(), testRoot.relativize(testFile.getParent()))); } From d0546fa696ffbc0d25c4617b19e18ff782c3af2d Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Thu, 11 Apr 2024 13:04:32 +0100 Subject: [PATCH 04/12] cleaner --- .../SlashingProtectionInterchangeTestExecutor.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index 75641bc1b62..610e9937528 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -68,18 +68,16 @@ private void runTest(final TestData testData, final Path tempDir) { final LocalSlashingProtector slashingProtector = new LocalSlashingProtector(SyncDataAccessor.create(tempDir), tempDir); testData.steps.forEach( - step -> - runStep(step, testData.genesisValidatorsRoot, importer, slashingProtector, tempDir)); + step -> runStep(step, testData.genesisValidatorsRoot, importer, slashingProtector)); } private void runStep( final Step step, final Bytes32 genesisValidatorsRoot, final SlashingProtectionImporter importer, - final LocalSlashingProtector slashingProtector, - final Path tempDir) { - final Path importFile = tempDir.resolve("import.yml"); + final LocalSlashingProtector slashingProtector) { try { + final Path importFile = Files.createTempFile("import", ".yml"); TestDataUtils.writeJsonToFile(step.interchange, importFile); importer.initialise(importFile.toFile()); // cleanup From cb2ab0bef70be99cce7af6c14f2529ac3e322902 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Thu, 11 Apr 2024 15:34:19 +0100 Subject: [PATCH 05/12] nit --- .../SlashingProtectionInterchangeTestExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index 610e9937528..72fb8bc77c9 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -49,7 +49,7 @@ public void runTest(final TestDefinition testDefinition) throws Throwable { // our implementation does not fail importing when the GVR in the interchange mismatches the one // from the spec if (testData.name.startsWith("wrong_genesis_validators_root")) { - LOG.info("Not going to run {}", testData.name); + LOG.info("Skipping {}", testData.name); return; } From 1cfdfa91186e484996dec4700acea7ac10b0d244 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Fri, 12 Apr 2024 08:58:36 +0100 Subject: [PATCH 06/12] small nits --- .../reference/ManualReferenceTestRunner.java | 2 +- ...hingProtectionInterchangeTestExecutor.java | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/ManualReferenceTestRunner.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/ManualReferenceTestRunner.java index 8db4b6cbafb..abdbc308224 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/ManualReferenceTestRunner.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/ManualReferenceTestRunner.java @@ -42,7 +42,7 @@ public class ManualReferenceTestRunner extends Eth2ReferenceTestCase { * *

May be overridden by the ENV_TEST_TYPE environment variable. */ - private static final String TEST_TYPE = "fork_choice"; + private static final String TEST_TYPE = ""; /** * Filter test to run to those from the specified spec. One of general, minimal or mainnet diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index 72fb8bc77c9..a94e8f3b3e5 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -76,15 +76,7 @@ private void runStep( final Bytes32 genesisValidatorsRoot, final SlashingProtectionImporter importer, final LocalSlashingProtector slashingProtector) { - try { - final Path importFile = Files.createTempFile("import", ".yml"); - TestDataUtils.writeJsonToFile(step.interchange, importFile); - importer.initialise(importFile.toFile()); - // cleanup - Files.delete(importFile); - } catch (IOException ex) { - throw new UncheckedIOException(ex); - } + importInterchange(importer, step.interchange); final Map importErrors = importer.updateLocalRecords(status -> LOG.info("Import status: " + status)); if (step.shouldSucceed) { @@ -108,6 +100,20 @@ private void runStep( .isCompletedWithValue(attestation.shouldSucceed)); } + private void importInterchange( + final SlashingProtectionImporter importer, + final SlashingProtectionInterchangeFormat interchange) { + try { + final Path importFile = Files.createTempFile("import", ".yml"); + TestDataUtils.writeJsonToFile(interchange, importFile); + importer.initialise(importFile.toFile()); + // cleanup + Files.delete(importFile); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + private void deleteDirectory(final Path dir) { try (DirectoryStream files = Files.newDirectoryStream(dir)) { for (Path file : files) { @@ -137,15 +143,13 @@ public record Block( BLSPublicKey pubkey, UInt64 slot, @JsonProperty("signing_root") Bytes32 signingRoot, - @JsonProperty("should_succeed") boolean shouldSucceed, - @JsonProperty("should_succeed_complete") boolean shouldSucceedComplete) {} + @JsonProperty("should_succeed") boolean shouldSucceed) {} public record Attestation( BLSPublicKey pubkey, @JsonProperty("source_epoch") UInt64 sourceEpoch, @JsonProperty("target_epoch") UInt64 targetEpoch, @JsonProperty("signing_root") Bytes32 signingRoot, - @JsonProperty("should_succeed") boolean shouldSucceed, - @JsonProperty("should_succeed_complete") boolean shouldSucceedComplete) {} + @JsonProperty("should_succeed") boolean shouldSucceed) {} } } From 460be01e4562dc9a02be93bfe908dd376fa774fb Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Fri, 12 Apr 2024 09:45:27 +0100 Subject: [PATCH 07/12] small refactor --- ...hingProtectionInterchangeTestExecutor.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index a94e8f3b3e5..d578c94cab8 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -46,8 +46,9 @@ public void runTest(final TestDefinition testDefinition) throws Throwable { final TestData testData = TestDataUtils.loadJson(testDefinition, testDefinition.getTestName(), TestData.class); - // our implementation does not fail importing when the GVR in the interchange mismatches the one - // from the spec + // our implementation fails when importing one of the keys in an interchange, which is already + // in our slashprotection directory with a different genesis validators root. However, the test + // does not import any keys if (testData.name.startsWith("wrong_genesis_validators_root")) { LOG.info("Skipping {}", testData.name); return; @@ -55,30 +56,28 @@ public void runTest(final TestDefinition testDefinition) throws Throwable { LOG.info("Running {}", testData.name); - final Path tempDir = Files.createTempDirectory(testData.name); + final Path slashingProtectionPath = Files.createTempDirectory("slashprotection"); try { - runTest(testData, tempDir); + runTest(testData, slashingProtectionPath); } finally { - deleteDirectory(tempDir); + deleteDirectory(slashingProtectionPath); } } - private void runTest(final TestData testData, final Path tempDir) { - final SlashingProtectionImporter importer = new SlashingProtectionImporter(tempDir); + private void runTest(final TestData testData, final Path slashingProtectionPath) { + final SlashingProtectionImporter importer = + new SlashingProtectionImporter(slashingProtectionPath); final LocalSlashingProtector slashingProtector = - new LocalSlashingProtector(SyncDataAccessor.create(tempDir), tempDir); - testData.steps.forEach( - step -> runStep(step, testData.genesisValidatorsRoot, importer, slashingProtector)); + new LocalSlashingProtector( + SyncDataAccessor.create(slashingProtectionPath), slashingProtectionPath); + testData.steps.forEach(step -> runStep(step, importer, slashingProtector)); } private void runStep( final Step step, - final Bytes32 genesisValidatorsRoot, final SlashingProtectionImporter importer, final LocalSlashingProtector slashingProtector) { - importInterchange(importer, step.interchange); - final Map importErrors = - importer.updateLocalRecords(status -> LOG.info("Import status: " + status)); + final Map importErrors = importInterchange(importer, step.interchange); if (step.shouldSucceed) { assertThat(importErrors).isEmpty(); } else { @@ -87,20 +86,21 @@ private void runStep( step.blocks.forEach( block -> assertThat( - slashingProtector.maySignBlock(block.pubkey, genesisValidatorsRoot, block.slot)) + slashingProtector.maySignBlock( + block.pubkey, step.interchange.metadata.genesisValidatorsRoot, block.slot)) .isCompletedWithValue(block.shouldSucceed)); step.attestations.forEach( attestation -> assertThat( slashingProtector.maySignAttestation( attestation.pubkey, - genesisValidatorsRoot, + step.interchange.metadata.genesisValidatorsRoot, attestation.sourceEpoch, attestation.targetEpoch)) .isCompletedWithValue(attestation.shouldSucceed)); } - private void importInterchange( + private Map importInterchange( final SlashingProtectionImporter importer, final SlashingProtectionInterchangeFormat interchange) { try { @@ -112,6 +112,7 @@ private void importInterchange( } catch (IOException ex) { throw new UncheckedIOException(ex); } + return importer.updateLocalRecords(status -> LOG.info("Import status: " + status)); } private void deleteDirectory(final Path dir) { From 29cd6b69493fc05e123ad4ce3e21003f3a3bd487 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Fri, 12 Apr 2024 09:46:32 +0100 Subject: [PATCH 08/12] add dot --- .../SlashingProtectionInterchangeTestExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index d578c94cab8..c3ce3dd4076 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -48,7 +48,7 @@ public void runTest(final TestDefinition testDefinition) throws Throwable { // our implementation fails when importing one of the keys in an interchange, which is already // in our slashprotection directory with a different genesis validators root. However, the test - // does not import any keys + // does not import any keys. if (testData.name.startsWith("wrong_genesis_validators_root")) { LOG.info("Skipping {}", testData.name); return; From f1dc1514ba0dbc4c0194a1ce0e55e350bc1b59ef Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Fri, 12 Apr 2024 09:54:17 +0100 Subject: [PATCH 09/12] sanity check --- .../SlashingProtectionInterchangeTestExecutor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index c3ce3dd4076..914d3871eca 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -23,6 +23,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; @@ -106,7 +107,8 @@ private Map importInterchange( try { final Path importFile = Files.createTempFile("import", ".yml"); TestDataUtils.writeJsonToFile(interchange, importFile); - importer.initialise(importFile.toFile()); + final Optional initialiseError = importer.initialise(importFile.toFile()); + assertThat(initialiseError).isEmpty(); // cleanup Files.delete(importFile); } catch (IOException ex) { From f7c451d95ebf1e77506a9ed1e3f5c86e73bced4e Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Fri, 12 Apr 2024 09:55:11 +0100 Subject: [PATCH 10/12] nit --- .../SlashingProtectionInterchangeTestExecutor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index 914d3871eca..38537f2125f 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -84,18 +84,18 @@ private void runStep( } else { assertThat(importErrors).isNotEmpty(); } + final Bytes32 genesisValidatorsRoot = step.interchange.metadata.genesisValidatorsRoot; step.blocks.forEach( block -> assertThat( - slashingProtector.maySignBlock( - block.pubkey, step.interchange.metadata.genesisValidatorsRoot, block.slot)) + slashingProtector.maySignBlock(block.pubkey, genesisValidatorsRoot, block.slot)) .isCompletedWithValue(block.shouldSucceed)); step.attestations.forEach( attestation -> assertThat( slashingProtector.maySignAttestation( attestation.pubkey, - step.interchange.metadata.genesisValidatorsRoot, + genesisValidatorsRoot, attestation.sourceEpoch, attestation.targetEpoch)) .isCompletedWithValue(attestation.shouldSucceed)); From 7882df316d0074fe4edb52f0d3051a698dec240a Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 15 Apr 2024 11:11:09 +0100 Subject: [PATCH 11/12] add test + clarifying comment --- .../data/SlashingProtectionImporterTest.java | 33 +++++++++++++++++-- .../src/test/resources/format2_minimal.json | 3 +- ...mal_different_genesis_validators_root.json | 22 +++++++++++++ ...hingProtectionInterchangeTestExecutor.java | 5 ++- 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 data/dataexchange/src/test/resources/format2_minimal_different_genesis_validators_root.json diff --git a/data/dataexchange/src/test/java/tech/pegasys/teku/data/SlashingProtectionImporterTest.java b/data/dataexchange/src/test/java/tech/pegasys/teku/data/SlashingProtectionImporterTest.java index 29f443e8e1e..0ac17428386 100644 --- a/data/dataexchange/src/test/java/tech/pegasys/teku/data/SlashingProtectionImporterTest.java +++ b/data/dataexchange/src/test/java/tech/pegasys/teku/data/SlashingProtectionImporterTest.java @@ -163,6 +163,31 @@ void shouldImportFileOverRepairedRecords(@TempDir Path tempDir) throws Exception repairedEpoch)); } + @Test + void shouldFailImportingIfValidatorExistingRecordHasDifferentGenesisValidatorsRoot( + @TempDir Path tempDir) throws URISyntaxException, IOException { + final SlashingProtectionImporter importer = new SlashingProtectionImporter(tempDir); + + final File slashProtection = getResourceFile("format2_minimal.json"); + + importer.initialise(slashProtection); + + Map errors = importer.updateLocalRecords(__ -> {}); + + assertThat(errors).isEmpty(); + + final File slashProtectionWithDifferentGvr = + getResourceFile("format2_minimal_different_genesis_validators_root.json"); + + importer.initialise(slashProtectionWithDifferentGvr); + + errors = importer.updateLocalRecords(__ -> {}); + + assertThat(errors) + .hasSize(1) + .containsEntry(publicKey, "Genesis validators root did not match what was expected."); + } + private ValidatorSigningRecord loadSigningRecord(final File repairedRuleFile) throws IOException { return ValidatorSigningRecord.fromBytes( Bytes.wrap(Files.readAllBytes(repairedRuleFile.toPath()))); @@ -182,9 +207,11 @@ private File usingResourceFile(final String resourceFileName, final Path tempDir throws URISyntaxException, IOException { final Path tempFile = tempDir.resolve(pubkey + ".yml").toAbsolutePath(); Files.copy( - new File(Resources.getResource(resourceFileName).toURI()).toPath(), - tempFile, - StandardCopyOption.REPLACE_EXISTING); + getResourceFile(resourceFileName).toPath(), tempFile, StandardCopyOption.REPLACE_EXISTING); return tempFile.toFile(); } + + private File getResourceFile(final String resourceFileName) throws URISyntaxException { + return new File(Resources.getResource(resourceFileName).toURI()); + } } diff --git a/data/dataexchange/src/test/resources/format2_minimal.json b/data/dataexchange/src/test/resources/format2_minimal.json index 9a4f1645fef..89c0bab40c1 100644 --- a/data/dataexchange/src/test/resources/format2_minimal.json +++ b/data/dataexchange/src/test/resources/format2_minimal.json @@ -7,7 +7,8 @@ { "pubkey": "0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed", "signed_blocks": [ - {"slot": "81952" + { + "slot": "81952" } ], "signed_attestations": [ diff --git a/data/dataexchange/src/test/resources/format2_minimal_different_genesis_validators_root.json b/data/dataexchange/src/test/resources/format2_minimal_different_genesis_validators_root.json new file mode 100644 index 00000000000..9a926c08fd6 --- /dev/null +++ b/data/dataexchange/src/test/resources/format2_minimal_different_genesis_validators_root.json @@ -0,0 +1,22 @@ +{ + "metadata": { + "interchange_format_version": "5", + "genesis_validators_root": "0x0000000000000000000000000000000000000000000000000000000000123457" + }, + "data": [ + { + "pubkey": "0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed", + "signed_blocks": [ + { + "slot": "81952" + } + ], + "signed_attestations": [ + { + "source_epoch": "2290", + "target_epoch": "3007" + } + ] + } + ] +} \ No newline at end of file diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index 38537f2125f..dbbb9f7eca1 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -49,7 +49,8 @@ public void runTest(final TestDefinition testDefinition) throws Throwable { // our implementation fails when importing one of the keys in an interchange, which is already // in our slashprotection directory with a different genesis validators root. However, the test - // does not import any keys. + // does not import any keys. This case is covered by + // SlashingProtectionImporterTest#shouldFailImportingIfValidatorExistingRecordHasDifferentGenesisValidatorsRoot() if (testData.name.startsWith("wrong_genesis_validators_root")) { LOG.info("Skipping {}", testData.name); return; @@ -137,6 +138,8 @@ public record TestData( public record Step( @JsonProperty("should_succeed") boolean shouldSucceed, + // we don't fail importing when the interchange contains slashable data, so can safely + // ignore this field in the tests @JsonProperty("contains_slashable_data") boolean containsSlashableData, SlashingProtectionInterchangeFormat interchange, List blocks, From fc743f2f29de620ebc9b4e189ecc92a1f1410460 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 15 Apr 2024 11:19:47 +0100 Subject: [PATCH 12/12] nit --- .../SlashingProtectionInterchangeTestExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java index dbbb9f7eca1..e9cb0119f85 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/slashing_protection_interchange/SlashingProtectionInterchangeTestExecutor.java @@ -106,7 +106,7 @@ private Map importInterchange( final SlashingProtectionImporter importer, final SlashingProtectionInterchangeFormat interchange) { try { - final Path importFile = Files.createTempFile("import", ".yml"); + final Path importFile = Files.createTempFile("import", ".json"); TestDataUtils.writeJsonToFile(interchange, importFile); final Optional initialiseError = importer.initialise(importFile.toFile()); assertThat(initialiseError).isEmpty();