diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7c2e38254..4a6f0977b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,3 +23,4 @@ the [releases page](https://github.com/Consensys/teku/releases). been updated to support older GLIBC versions (ie Ubuntu 20.04 and Debian 11). ### Bug Fixes +- During network configuration load, all missing fields will now be reported, rather than just the first missing field causing failure. diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java index 57e2ed00073..8df3491ab14 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java @@ -30,13 +30,12 @@ import tech.pegasys.teku.spec.networks.Eth2Presets; public class SpecConfigLoader { + private static final Logger LOG = LogManager.getLogger(); private static final List AVAILABLE_PRESETS = List.of("phase0", "altair", "bellatrix", "capella", "deneb"); private static final String CONFIG_PATH = "configs/"; private static final String PRESET_PATH = "presets/"; - private static final Logger LOG = LogManager.getLogger(); - public static SpecConfig loadConfigStrict(final String configName) { return loadConfig(configName, false, __ -> {}); } @@ -62,6 +61,13 @@ public static SpecConfig loadConfig( public static SpecConfig loadRemoteConfig( final Map config, final Consumer modifier) { final SpecConfigReader reader = new SpecConfigReader(); + if (config.containsKey(SpecConfigReader.PRESET_KEY)) { + try { + applyPreset("remote", reader, true, config.get(SpecConfigReader.PRESET_KEY)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } if (config.containsKey(SpecConfigReader.CONFIG_NAME_KEY)) { final String configNameKey = config.get(SpecConfigReader.CONFIG_NAME_KEY); try { @@ -73,13 +79,6 @@ public static SpecConfig loadRemoteConfig( exception::getMessage); } } - if (config.containsKey(SpecConfigReader.PRESET_KEY)) { - try { - applyPreset("remote", reader, true, config.get(SpecConfigReader.PRESET_KEY)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } reader.loadFromMap(config, true); return reader.build(modifier); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/AltairBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/AltairBuilder.java index 7d347a8fb93..bd30f3552b9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/AltairBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/AltairBuilder.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; +import java.util.HashMap; +import java.util.Map; import java.util.function.BiConsumer; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -95,19 +97,7 @@ public void validate() { SpecBuilderUtil.fillMissingValuesWithZeros(this); } - SpecBuilderUtil.validateConstant( - "inactivityPenaltyQuotientAltair", inactivityPenaltyQuotientAltair); - SpecBuilderUtil.validateConstant( - "minSlashingPenaltyQuotientAltair", minSlashingPenaltyQuotientAltair); - SpecBuilderUtil.validateConstant( - "proportionalSlashingMultiplierAltair", proportionalSlashingMultiplierAltair); - SpecBuilderUtil.validateConstant("syncCommitteeSize", syncCommitteeSize); - SpecBuilderUtil.validateConstant("inactivityScoreBias", inactivityScoreBias); - SpecBuilderUtil.validateConstant("inactivityScoreRecoveryRate", inactivityScoreRecoveryRate); - SpecBuilderUtil.validateConstant("epochsPerSyncCommitteePeriod", epochsPerSyncCommitteePeriod); - SpecBuilderUtil.validateConstant("altairForkVersion", altairForkVersion); - SpecBuilderUtil.validateConstant("altairForkEpoch", altairForkEpoch); - SpecBuilderUtil.validateConstant("minSyncCommitteeParticipants", minSyncCommitteeParticipants); + validateConstants(); // Config items were added after launch so provide defaults to preserve compatibility if (updateTimeout == null) { @@ -115,6 +105,22 @@ public void validate() { } } + @Override + public Map getValidationMap() { + final Map constants = new HashMap<>(); + constants.put("inactivityPenaltyQuotientAltair", inactivityPenaltyQuotientAltair); + constants.put("minSlashingPenaltyQuotientAltair", minSlashingPenaltyQuotientAltair); + constants.put("proportionalSlashingMultiplierAltair", proportionalSlashingMultiplierAltair); + constants.put("syncCommitteeSize", syncCommitteeSize); + constants.put("inactivityScoreBias", inactivityScoreBias); + constants.put("inactivityScoreRecoveryRate", inactivityScoreRecoveryRate); + constants.put("epochsPerSyncCommitteePeriod", epochsPerSyncCommitteePeriod); + constants.put("altairForkVersion", altairForkVersion); + constants.put("altairForkEpoch", altairForkEpoch); + constants.put("minSyncCommitteeParticipants", minSyncCommitteeParticipants); + return constants; + } + @Override public void addOverridableItemsToRawConfig(final BiConsumer rawConfig) { rawConfig.accept("ALTAIR_FORK_EPOCH", altairForkEpoch); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BellatrixBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BellatrixBuilder.java index 5bba70d7f0d..e6d6bc53c07 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BellatrixBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BellatrixBuilder.java @@ -18,6 +18,8 @@ import static tech.pegasys.teku.spec.constants.NetworkConstants.DEFAULT_SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY; import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; import java.util.function.BiConsumer; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -96,18 +98,23 @@ public void validate() { SpecBuilderUtil.fillMissingValuesWithZeros(this); } - SpecBuilderUtil.validateConstant("bellatrixForkVersion", bellatrixForkVersion); - SpecBuilderUtil.validateConstant("bellatrixForkEpoch", bellatrixForkEpoch); - SpecBuilderUtil.validateConstant( - "inactivityPenaltyQuotientBellatrix", inactivityPenaltyQuotientBellatrix); - SpecBuilderUtil.validateConstant( - "minSlashingPenaltyQuotientBellatrix", minSlashingPenaltyQuotientBellatrix); - SpecBuilderUtil.validateConstant( + validateConstants(); + } + + @Override + public Map getValidationMap() { + final Map constants = new HashMap<>(); + constants.put("bellatrixForkVersion", bellatrixForkVersion); + constants.put("bellatrixForkEpoch", bellatrixForkEpoch); + constants.put("inactivityPenaltyQuotientBellatrix", inactivityPenaltyQuotientBellatrix); + constants.put("minSlashingPenaltyQuotientBellatrix", minSlashingPenaltyQuotientBellatrix); + constants.put( "proportionalSlashingMultiplierBellatrix", proportionalSlashingMultiplierBellatrix); - SpecBuilderUtil.validateConstant("maxBytesPerTransaction", maxBytesPerTransaction); - SpecBuilderUtil.validateConstant("maxTransactionsPerPayload", maxTransactionsPerPayload); - SpecBuilderUtil.validateConstant("bytesPerLogsBloom", bytesPerLogsBloom); - SpecBuilderUtil.validateConstant("maxExtraDataBytes", maxExtraDataBytes); + constants.put("maxBytesPerTransaction", maxBytesPerTransaction); + constants.put("maxTransactionsPerPayload", maxTransactionsPerPayload); + constants.put("bytesPerLogsBloom", bytesPerLogsBloom); + constants.put("maxExtraDataBytes", maxExtraDataBytes); + return constants; } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BuilderChain.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BuilderChain.java index e705e453337..7e2d99a6817 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BuilderChain.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/BuilderChain.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.spec.config.builder; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; import tech.pegasys.teku.spec.config.SpecConfig; @@ -86,6 +87,11 @@ public void validate() { tail.validate(); } + @Override + public Map getValidationMap() { + return Map.of(); + } + private static class NoOpForkBuilder implements ForkConfigBuilder { @Override @@ -96,6 +102,11 @@ public T build(final T specConfig) { @Override public void validate() {} + @Override + public Map getValidationMap() { + return Map.of(); + } + @Override public void addOverridableItemsToRawConfig(final BiConsumer rawConfig) {} } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/CapellaBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/CapellaBuilder.java index 942a5e28dfa..5d8736dd9f2 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/CapellaBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/CapellaBuilder.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; +import java.util.HashMap; +import java.util.Map; import java.util.function.BiConsumer; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -93,12 +95,19 @@ public void validate() { SpecBuilderUtil.fillMissingValuesWithZeros(this); } - SpecBuilderUtil.validateConstant("capellaForkVersion", capellaForkVersion); - SpecBuilderUtil.validateConstant("capellaForkEpoch", capellaForkEpoch); - SpecBuilderUtil.validateConstant("maxBlsToExecutionChanges", maxBlsToExecutionChanges); - SpecBuilderUtil.validateConstant("maxWithdrawalsPerPayload", maxWithdrawalsPerPayload); - SpecBuilderUtil.validateConstant( - "maxValidatorsPerWithdrawalSweep", maxValidatorsPerWithdrawalSweep); + validateConstants(); + } + + @Override + public Map getValidationMap() { + final Map constants = new HashMap<>(); + constants.put("capellaForkVersion", capellaForkVersion); + constants.put("capellaForkEpoch", capellaForkEpoch); + constants.put("maxBlsToExecutionChanges", maxBlsToExecutionChanges); + constants.put("maxWithdrawalsPerPayload", maxWithdrawalsPerPayload); + constants.put("maxValidatorsPerWithdrawalSweep", maxValidatorsPerWithdrawalSweep); + + return constants; } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/DenebBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/DenebBuilder.java index 6ac9efc48e9..058d1649fe0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/DenebBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/DenebBuilder.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import tech.pegasys.teku.infrastructure.bytes.Bytes4; @@ -147,23 +149,31 @@ public void validate() { SpecBuilderUtil.fillMissingValuesWithZeros(this); } - SpecBuilderUtil.validateConstant("denebForkEpoch", denebForkEpoch); - SpecBuilderUtil.validateConstant("denebForkVersion", denebForkVersion); - SpecBuilderUtil.validateConstant( - "maxPerEpochActivationChurnLimit", maxPerEpochActivationChurnLimit); - SpecBuilderUtil.validateConstant("fieldElementsPerBlob", fieldElementsPerBlob); - SpecBuilderUtil.validateConstant("maxBlobCommitmentsPerBlock", maxBlobCommitmentsPerBlock); - SpecBuilderUtil.validateConstant("maxBlobsPerBlock", maxBlobsPerBlock); - SpecBuilderUtil.validateConstant("maxRequestBlocksDeneb", maxRequestBlocksDeneb); - SpecBuilderUtil.validateConstant("maxRequestBlobSidecars", maxRequestBlobSidecars); - SpecBuilderUtil.validateConstant( - "minEpochsForBlobSidecarsRequests", minEpochsForBlobSidecarsRequests); - SpecBuilderUtil.validateConstant("blobSidecarSubnetCount", blobSidecarSubnetCount); + validateConstants(); + if (!denebForkEpoch.equals(SpecConfig.FAR_FUTURE_EPOCH) && !kzgNoop) { SpecBuilderUtil.validateRequiredOptional("trustedSetupPath", trustedSetupPath); } } + @Override + public Map getValidationMap() { + final Map constants = new HashMap<>(); + + constants.put("denebForkEpoch", denebForkEpoch); + constants.put("denebForkVersion", denebForkVersion); + constants.put("maxPerEpochActivationChurnLimit", maxPerEpochActivationChurnLimit); + constants.put("fieldElementsPerBlob", fieldElementsPerBlob); + constants.put("maxBlobCommitmentsPerBlock", maxBlobCommitmentsPerBlock); + constants.put("maxBlobsPerBlock", maxBlobsPerBlock); + constants.put("maxRequestBlocksDeneb", maxRequestBlocksDeneb); + constants.put("maxRequestBlobSidecars", maxRequestBlobSidecars); + constants.put("minEpochsForBlobSidecarsRequests", minEpochsForBlobSidecarsRequests); + constants.put("blobSidecarSubnetCount", blobSidecarSubnetCount); + + return constants; + } + @Override public void addOverridableItemsToRawConfig(final BiConsumer rawConfig) { rawConfig.accept("DENEB_FORK_EPOCH", denebForkEpoch); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ForkConfigBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ForkConfigBuilder.java index e04ecadfc1a..d811492d378 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ForkConfigBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ForkConfigBuilder.java @@ -13,6 +13,10 @@ package tech.pegasys.teku.spec.config.builder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.BiConsumer; import tech.pegasys.teku.spec.config.SpecConfig; @@ -22,5 +26,24 @@ interface ForkConfigBuilder getValidationMap(); + + default void validateConstants() { + final List> maybeErrors = new ArrayList<>(); + final Map constants = getValidationMap(); + + constants.forEach((k, v) -> maybeErrors.add(SpecBuilderUtil.validateConstant(k, v))); + + final List fieldsFailingValidation = + maybeErrors.stream().filter(Optional::isPresent).map(Optional::get).toList(); + + if (!fieldsFailingValidation.isEmpty()) { + throw new IllegalArgumentException( + String.format( + "The specified network configuration had missing or invalid values for constants %s", + String.join(", ", fieldsFailingValidation))); + } + } + void addOverridableItemsToRawConfig(BiConsumer rawConfig); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecBuilderUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecBuilderUtil.java index 0fe90ad45d3..a47f21294df 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecBuilderUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecBuilderUtil.java @@ -13,13 +13,14 @@ package tech.pegasys.teku.spec.config.builder; -import static com.google.common.base.Preconditions.checkArgument; import static tech.pegasys.teku.spec.config.SpecConfigFormatter.camelToSnakeCase; import java.lang.reflect.Modifier; import java.util.Arrays; 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.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -27,6 +28,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; public class SpecBuilderUtil { + private static final Logger LOG = LogManager.getLogger(); private static final Map, Object> DEFAULT_ZERO_VALUES = Map.of( UInt64.class, @@ -45,26 +47,37 @@ public class SpecBuilderUtil { // Placeholder version explicitly doesn't match MainNet (or any other known testnet) static final Bytes4 PLACEHOLDER_FORK_VERSION = Bytes4.fromHexString("0x99999999"); - static void validateConstant(final String name, final Object value) { - validateNotNull(name, value); + static Optional validateConstant(final String name, final Object value) { + return validateNotNull(name, value); } - static void validateConstant(final String name, final Long value) { - validateNotNull(name, value); - checkArgument(value >= 0, "Long values must be positive"); + static Optional validateConstant(final String name, final Integer value) { + final Optional maybeError = validateNotNull(name, value); + if (maybeError.isPresent()) { + return maybeError; + } + if (value < 0) { + LOG.error( + "Value for constant '{}' ({}) failed to validate - Integer values must be positive", + name, + value); + return Optional.of(name); + } + return Optional.empty(); } - static void validateConstant(final String name, final Integer value) { - validateNotNull(name, value); - checkArgument(value >= 0, "Integer values must be positive"); + private static Optional validateNotNull(final String name, final Object value) { + if (value == null) { + return Optional.of(camelToSnakeCase(name)); + } + return Optional.empty(); } - static void validateNotNull(final String name, final Object value) { - checkArgument(value != null, "Missing value for spec constant '%s'", camelToSnakeCase(name)); - } - - static void validateRequiredOptional(final String name, final Optional value) { - checkArgument(value.isPresent(), "Missing value for required '%s'", name); + static Optional validateRequiredOptional(final String name, final Optional value) { + if (value.isEmpty()) { + return Optional.of(name); + } + return Optional.empty(); } static void fillMissingValuesWithZeros(final ForkConfigBuilder builder) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java index b45dfc4dd43..b141fc8cf7e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java @@ -16,9 +16,14 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import tech.pegasys.teku.ethereum.execution.types.Eth1Address; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -28,7 +33,7 @@ @SuppressWarnings({"UnusedReturnValue", "unused"}) public class SpecConfigBuilder { - + private static final Logger LOG = LogManager.getLogger(); private final Map rawConfig = new HashMap<>(); // Misc @@ -203,76 +208,91 @@ public SpecConfig build() { return builderChain.build(config); } + private Map getValidationMap() { + final Map constants = new HashMap<>(); + constants.put("eth1FollowDistance", eth1FollowDistance); + constants.put("maxCommitteesPerSlot", maxCommitteesPerSlot); + constants.put("targetCommitteeSize", targetCommitteeSize); + constants.put("maxValidatorsPerCommittee", maxValidatorsPerCommittee); + constants.put("minPerEpochChurnLimit", minPerEpochChurnLimit); + constants.put("churnLimitQuotient", churnLimitQuotient); + constants.put("shuffleRoundCount", shuffleRoundCount); + constants.put("minGenesisActiveValidatorCount", minGenesisActiveValidatorCount); + constants.put("minGenesisTime", minGenesisTime); + constants.put("hysteresisQuotient", hysteresisQuotient); + constants.put("hysteresisDownwardMultiplier", hysteresisDownwardMultiplier); + constants.put("hysteresisUpwardMultiplier", hysteresisUpwardMultiplier); + constants.put("proportionalSlashingMultiplier", proportionalSlashingMultiplier); + constants.put("minDepositAmount", minDepositAmount); + constants.put("maxEffectiveBalance", maxEffectiveBalance); + constants.put("ejectionBalance", ejectionBalance); + constants.put("effectiveBalanceIncrement", effectiveBalanceIncrement); + constants.put("genesisForkVersion", genesisForkVersion); + constants.put("genesisDelay", genesisDelay); + constants.put("secondsPerSlot", secondsPerSlot); + constants.put("minAttestationInclusionDelay", minAttestationInclusionDelay); + constants.put("slotsPerEpoch", slotsPerEpoch); + constants.put("minSeedLookahead", minSeedLookahead); + constants.put("maxSeedLookahead", maxSeedLookahead); + constants.put("minEpochsToInactivityPenalty", minEpochsToInactivityPenalty); + constants.put("epochsPerEth1VotingPeriod", epochsPerEth1VotingPeriod); + constants.put("slotsPerHistoricalRoot", slotsPerHistoricalRoot); + constants.put("minValidatorWithdrawabilityDelay", minValidatorWithdrawabilityDelay); + constants.put("shardCommitteePeriod", shardCommitteePeriod); + constants.put("epochsPerHistoricalVector", epochsPerHistoricalVector); + constants.put("epochsPerSlashingsVector", epochsPerSlashingsVector); + constants.put("historicalRootsLimit", historicalRootsLimit); + constants.put("validatorRegistryLimit", validatorRegistryLimit); + constants.put("baseRewardFactor", baseRewardFactor); + constants.put("whistleblowerRewardQuotient", whistleblowerRewardQuotient); + constants.put("proposerRewardQuotient", proposerRewardQuotient); + constants.put("inactivityPenaltyQuotient", inactivityPenaltyQuotient); + constants.put("minSlashingPenaltyQuotient", minSlashingPenaltyQuotient); + constants.put("maxProposerSlashings", maxProposerSlashings); + constants.put("maxAttesterSlashings", maxAttesterSlashings); + constants.put("maxAttestations", maxAttestations); + constants.put("maxDeposits", maxDeposits); + constants.put("maxVoluntaryExits", maxVoluntaryExits); + constants.put("secondsPerEth1Block", secondsPerEth1Block); + constants.put("safeSlotsToUpdateJustified", safeSlotsToUpdateJustified); + constants.put("depositChainId", depositChainId); + constants.put("depositNetworkId", depositNetworkId); + constants.put("depositContractAddress", depositContractAddress); + + constants.put("gossipMaxSize", gossipMaxSize); + constants.put("maxChunkSize", maxChunkSize); + constants.put("maxRequestBlocks", maxRequestBlocks); + constants.put("epochsPerSubnetSubscription", epochsPerSubnetSubscription); + constants.put("minEpochsForBlockRequests", minEpochsForBlockRequests); + constants.put("ttfbTimeout", ttfbTimeout); + constants.put("respTimeout", respTimeout); + constants.put("attestationPropagationSlotRange", attestationPropagationSlotRange); + constants.put("maximumGossipClockDisparity", maximumGossipClockDisparity); + constants.put("messageDomainInvalidSnappy", messageDomainInvalidSnappy); + constants.put("messageDomainValidSnappy", messageDomainValidSnappy); + constants.put("subnetsPerNode", subnetsPerNode); + constants.put("attestationSubnetCount", attestationSubnetCount); + constants.put("attestationSubnetExtraBits", attestationSubnetExtraBits); + constants.put("attestationSubnetPrefixBits", attestationSubnetPrefixBits); + return constants; + } + private void validate() { - checkArgument(rawConfig.size() > 0, "Raw spec config must be provided"); - SpecBuilderUtil.validateConstant("eth1FollowDistance", eth1FollowDistance); - SpecBuilderUtil.validateConstant("maxCommitteesPerSlot", maxCommitteesPerSlot); - SpecBuilderUtil.validateConstant("targetCommitteeSize", targetCommitteeSize); - SpecBuilderUtil.validateConstant("maxValidatorsPerCommittee", maxValidatorsPerCommittee); - SpecBuilderUtil.validateConstant("minPerEpochChurnLimit", minPerEpochChurnLimit); - SpecBuilderUtil.validateConstant("churnLimitQuotient", churnLimitQuotient); - SpecBuilderUtil.validateConstant("shuffleRoundCount", shuffleRoundCount); - SpecBuilderUtil.validateConstant( - "minGenesisActiveValidatorCount", minGenesisActiveValidatorCount); - SpecBuilderUtil.validateConstant("minGenesisTime", minGenesisTime); - SpecBuilderUtil.validateConstant("hysteresisQuotient", hysteresisQuotient); - SpecBuilderUtil.validateConstant("hysteresisDownwardMultiplier", hysteresisDownwardMultiplier); - SpecBuilderUtil.validateConstant("hysteresisUpwardMultiplier", hysteresisUpwardMultiplier); - SpecBuilderUtil.validateConstant( - "proportionalSlashingMultiplier", proportionalSlashingMultiplier); - SpecBuilderUtil.validateConstant("minDepositAmount", minDepositAmount); - SpecBuilderUtil.validateConstant("maxEffectiveBalance", maxEffectiveBalance); - SpecBuilderUtil.validateConstant("ejectionBalance", ejectionBalance); - SpecBuilderUtil.validateConstant("effectiveBalanceIncrement", effectiveBalanceIncrement); - SpecBuilderUtil.validateConstant("genesisForkVersion", genesisForkVersion); - SpecBuilderUtil.validateConstant("genesisDelay", genesisDelay); - SpecBuilderUtil.validateConstant("secondsPerSlot", secondsPerSlot); - SpecBuilderUtil.validateConstant("minAttestationInclusionDelay", minAttestationInclusionDelay); - SpecBuilderUtil.validateConstant("slotsPerEpoch", slotsPerEpoch); - SpecBuilderUtil.validateConstant("minSeedLookahead", minSeedLookahead); - SpecBuilderUtil.validateConstant("maxSeedLookahead", maxSeedLookahead); - SpecBuilderUtil.validateConstant("minEpochsToInactivityPenalty", minEpochsToInactivityPenalty); - SpecBuilderUtil.validateConstant("epochsPerEth1VotingPeriod", epochsPerEth1VotingPeriod); - SpecBuilderUtil.validateConstant("slotsPerHistoricalRoot", slotsPerHistoricalRoot); - SpecBuilderUtil.validateConstant( - "minValidatorWithdrawabilityDelay", minValidatorWithdrawabilityDelay); - SpecBuilderUtil.validateConstant("shardCommitteePeriod", shardCommitteePeriod); - SpecBuilderUtil.validateConstant("epochsPerHistoricalVector", epochsPerHistoricalVector); - SpecBuilderUtil.validateConstant("epochsPerSlashingsVector", epochsPerSlashingsVector); - SpecBuilderUtil.validateConstant("historicalRootsLimit", historicalRootsLimit); - SpecBuilderUtil.validateConstant("validatorRegistryLimit", validatorRegistryLimit); - SpecBuilderUtil.validateConstant("baseRewardFactor", baseRewardFactor); - SpecBuilderUtil.validateConstant("whistleblowerRewardQuotient", whistleblowerRewardQuotient); - SpecBuilderUtil.validateConstant("proposerRewardQuotient", proposerRewardQuotient); - SpecBuilderUtil.validateConstant("inactivityPenaltyQuotient", inactivityPenaltyQuotient); - SpecBuilderUtil.validateConstant("minSlashingPenaltyQuotient", minSlashingPenaltyQuotient); - SpecBuilderUtil.validateConstant("maxProposerSlashings", maxProposerSlashings); - SpecBuilderUtil.validateConstant("maxAttesterSlashings", maxAttesterSlashings); - SpecBuilderUtil.validateConstant("maxAttestations", maxAttestations); - SpecBuilderUtil.validateConstant("maxDeposits", maxDeposits); - SpecBuilderUtil.validateConstant("maxVoluntaryExits", maxVoluntaryExits); - SpecBuilderUtil.validateConstant("secondsPerEth1Block", secondsPerEth1Block); - SpecBuilderUtil.validateConstant("safeSlotsToUpdateJustified", safeSlotsToUpdateJustified); - SpecBuilderUtil.validateConstant("depositChainId", depositChainId); - SpecBuilderUtil.validateConstant("depositNetworkId", depositNetworkId); - SpecBuilderUtil.validateConstant("depositContractAddress", depositContractAddress); - - SpecBuilderUtil.validateConstant("gossipMaxSize", gossipMaxSize); - SpecBuilderUtil.validateConstant("maxChunkSize", maxChunkSize); - SpecBuilderUtil.validateConstant("maxRequestBlocks", maxRequestBlocks); - SpecBuilderUtil.validateConstant("epochsPerSubnetSubscription", epochsPerSubnetSubscription); - SpecBuilderUtil.validateConstant("minEpochsForBlockRequests", minEpochsForBlockRequests); - SpecBuilderUtil.validateConstant("ttfbTimeout", ttfbTimeout); - SpecBuilderUtil.validateConstant("respTimeout", respTimeout); - SpecBuilderUtil.validateConstant( - "attestationPropagationSlotRange", attestationPropagationSlotRange); - SpecBuilderUtil.validateConstant("maximumGossipClockDisparity", maximumGossipClockDisparity); - SpecBuilderUtil.validateConstant("messageDomainInvalidSnappy", messageDomainInvalidSnappy); - SpecBuilderUtil.validateConstant("messageDomainValidSnappy", messageDomainValidSnappy); - SpecBuilderUtil.validateConstant("subnetsPerNode", subnetsPerNode); - SpecBuilderUtil.validateConstant("attestationSubnetCount", attestationSubnetCount); - SpecBuilderUtil.validateConstant("attestationSubnetExtraBits", attestationSubnetExtraBits); - SpecBuilderUtil.validateConstant("attestationSubnetPrefixBits", attestationSubnetPrefixBits); + checkArgument(!rawConfig.isEmpty(), "Raw spec config must be provided"); + final List> maybeErrors = new ArrayList<>(); + final Map constants = getValidationMap(); + + constants.forEach((k, v) -> maybeErrors.add(SpecBuilderUtil.validateConstant(k, v))); + + final List fieldsFailingValidation = + maybeErrors.stream().filter(Optional::isPresent).map(Optional::get).toList(); + + if (!fieldsFailingValidation.isEmpty()) { + throw new IllegalArgumentException( + String.format( + "The specified network configuration had missing or invalid values for constants %s", + String.join(", ", fieldsFailingValidation))); + } builderChain.validate(); } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigReaderTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigReaderTest.java index 256f5090c00..a2c0c58aeca 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigReaderTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigReaderTest.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.spec.config; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static tech.pegasys.teku.spec.config.SpecConfigAssertions.assertAllAltairFieldsSet; import java.io.IOException; @@ -31,7 +32,7 @@ public void read_missingConfig() { assertThatThrownBy(reader::build) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Missing value for spec constant 'MIN_PER_EPOCH_CHURN_LIMIT'"); + .hasMessageContaining("MIN_PER_EPOCH_CHURN_LIMIT"); } @Test @@ -49,7 +50,7 @@ public void read_missingAltairConstant() { bellatrixBuilder -> bellatrixBuilder.bellatrixForkEpoch(UInt64.ZERO)))) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Missing value for spec constant 'EPOCHS_PER_SYNC_COMMITTEE_PERIOD'"); + .hasMessageContaining("EPOCHS_PER_SYNC_COMMITTEE_PERIOD"); } @Test @@ -86,9 +87,17 @@ public void read_emptyFile() { public void read_almostEmptyFile() { processFileAsInputStream(getInvalidConfigPath("almostEmpty"), this::readConfig); - assertThatThrownBy(reader::build) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Missing value for spec constant"); + try { + reader.build(); + Assertions.fail("Should have received an exception"); + } catch (IllegalArgumentException e) { + final String message = e.getMessage(); + assertThat(message).contains("missing or invalid values for constants"); + // this message contains a number of items separated by ", ". + // If there's only one field, then this test will return 1 element, but it should return all + // missing fields. + assertThat(message.split(", ")).hasSizeGreaterThan(1); + } } @Test diff --git a/teku/src/test/java/tech/pegasys/teku/cli/subcommand/RemoteSpecLoaderTest.java b/teku/src/test/java/tech/pegasys/teku/cli/subcommand/RemoteSpecLoaderTest.java index 777dd0b4d55..5e3839708c1 100644 --- a/teku/src/test/java/tech/pegasys/teku/cli/subcommand/RemoteSpecLoaderTest.java +++ b/teku/src/test/java/tech/pegasys/teku/cli/subcommand/RemoteSpecLoaderTest.java @@ -65,7 +65,7 @@ void shouldFillWhenRequiredItemsAreMissing() { } @Test - void shouldProvideValidSpecConfigWithIncompleteRemoteConfig() throws IOException { + void shouldDefaultNetworkConfigThatMovedFromConstants() throws IOException { final String jsonConfig = Resources.toString( Resources.getResource(RemoteSpecLoaderTest.class, "config_missing_network_fields.json"),