diff --git a/CHANGELOG.md b/CHANGELOG.md index 28605f5f..16e4a9a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,13 @@ All notable changes to this project will be documented in this file. ## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-blockchain-adapter-api/releases/tag/vNEXT) 2023 -### New Features -- Remove `nexus.intra.iex.ec` repository. (#96) ### Bug Fixes - Fix and harmonize `Dockerfile entrypoint` in all Spring Boot applications. (#102) ### Quality +- Remove `nexus.intra.iex.ec` repository. (#96) - Upgrade to Gradle 8.2.1 with up-to-date plugins. (#100) - Clean TODOs. (#104) +- `ChainConfig` instance is immutable and validated. Application will fail to start if chain config parameters violate constraints. (#105) ### Dependency Upgrades - Upgrade to `eclipse-temurin` 11.0.20. (#98) - Upgrade to Spring Boot 2.7.14. (#99) diff --git a/src/main/java/com/iexec/blockchain/Application.java b/src/main/java/com/iexec/blockchain/Application.java index 6f53d976..480b9934 100644 --- a/src/main/java/com/iexec/blockchain/Application.java +++ b/src/main/java/com/iexec/blockchain/Application.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication @EnableScheduling +@SpringBootApplication +@ConfigurationPropertiesScan public class Application { public static void main(String[] args) { diff --git a/src/main/java/com/iexec/blockchain/config/PublicConfigurationController.java b/src/main/java/com/iexec/blockchain/config/PublicConfigurationController.java index bafd1029..fc1557a0 100644 --- a/src/main/java/com/iexec/blockchain/config/PublicConfigurationController.java +++ b/src/main/java/com/iexec/blockchain/config/PublicConfigurationController.java @@ -39,10 +39,10 @@ public PublicConfigurationController(ChainConfig chainConfig) { */ @GetMapping("/chain") public ResponseEntity getPublicChainConfig() { - final Integer blockTime = chainConfig.getBlockTime(); + final int blockTime = chainConfig.getBlockTime(); final PublicChainConfig publicChainConfig = PublicChainConfig .builder() - .chainId(chainConfig.getChainId()) + .chainId(chainConfig.getId()) .sidechain(chainConfig.isSidechain()) .chainNodeUrl(chainConfig.getNodeAddress()) .iexecHubContractAddress(chainConfig.getHubAddress()) diff --git a/src/main/java/com/iexec/blockchain/signer/SignerService.java b/src/main/java/com/iexec/blockchain/signer/SignerService.java index 9dd5b184..1d2e7088 100644 --- a/src/main/java/com/iexec/blockchain/signer/SignerService.java +++ b/src/main/java/com/iexec/blockchain/signer/SignerService.java @@ -31,7 +31,8 @@ public class SignerService { private final OrderSigner orderSigner; public SignerService(ChainConfig chainConfig, CredentialsService credentialsService) { - this.orderSigner = new OrderSigner(chainConfig.getChainId(), + this.orderSigner = new OrderSigner( + chainConfig.getId(), chainConfig.getHubAddress(), credentialsService.getCredentials().getEcKeyPair()); } diff --git a/src/main/java/com/iexec/blockchain/tool/ChainConfig.java b/src/main/java/com/iexec/blockchain/tool/ChainConfig.java index d7fe695d..59585aab 100644 --- a/src/main/java/com/iexec/blockchain/tool/ChainConfig.java +++ b/src/main/java/com/iexec/blockchain/tool/ChainConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2023 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,66 +17,56 @@ package com.iexec.blockchain.tool; import com.iexec.common.chain.validation.ValidNonZeroEthereumAddress; -import lombok.*; +import lombok.Builder; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; import org.hibernate.validator.constraints.URL; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; import javax.annotation.PostConstruct; -import javax.validation.ConstraintViolationException; -import javax.validation.Validator; +import javax.validation.*; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Positive; +import java.util.Set; -@Component -@Getter -@ToString +@Slf4j +@Value @Builder -@AllArgsConstructor +@ConstructorBinding +@ConfigurationProperties(prefix = "chain") public class ChainConfig { - @Value("${chain.id}") @Positive(message = "Chain id should be positive") @NotNull - private Integer chainId; + int id; - @Value("${chain.node-address}") @URL @NotEmpty - private String nodeAddress; + String nodeAddress; - @Value("${chain.block-time}") @Positive(message = "Block time should be positive") @NotNull - private Integer blockTime; + int blockTime; - @Value("${chain.hub-address}") @ValidNonZeroEthereumAddress - private String hubAddress; + String hubAddress; - @Value("${chain.is-sidechain}") - private boolean isSidechain; + boolean isSidechain; - @Value("${chain.gas-price-multiplier}") - private float gasPriceMultiplier; + float gasPriceMultiplier; - @Value("${chain.gas-price-cap}") - private long gasPriceCap; - - @Getter(AccessLevel.NONE) // no getter - private final Validator validator; - - @Autowired - public ChainConfig(Validator validator) { - this.validator = validator; - } + long gasPriceCap; @PostConstruct private void validate() { - if (!validator.validate(this).isEmpty()) { - throw new ConstraintViolationException(validator.validate(this)); + try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { + Validator validator = factory.getValidator(); + Set> violations = validator.validate(this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } } } } diff --git a/src/main/java/com/iexec/blockchain/tool/Web3jService.java b/src/main/java/com/iexec/blockchain/tool/Web3jService.java index 4d6f8f66..91da3db0 100644 --- a/src/main/java/com/iexec/blockchain/tool/Web3jService.java +++ b/src/main/java/com/iexec/blockchain/tool/Web3jService.java @@ -26,7 +26,7 @@ public class Web3jService extends Web3jAbstractService { public Web3jService(ChainConfig chainConfig) { super( - chainConfig.getChainId(), + chainConfig.getId(), chainConfig.getNodeAddress(), Duration.ofSeconds(chainConfig.getBlockTime()), chainConfig.getGasPriceMultiplier(), diff --git a/src/test/java/com/iexec/blockchain/config/PublicConfigurationControllerTests.java b/src/test/java/com/iexec/blockchain/config/PublicConfigurationControllerTests.java index 2ddca302..bec04b91 100644 --- a/src/test/java/com/iexec/blockchain/config/PublicConfigurationControllerTests.java +++ b/src/test/java/com/iexec/blockchain/config/PublicConfigurationControllerTests.java @@ -20,45 +20,45 @@ import com.iexec.common.config.PublicChainConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.springframework.http.ResponseEntity; import java.time.Duration; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.when; class PublicConfigurationControllerTests { - @Mock - ChainConfig chainConfig; + private static final int ID = 65535; + private static final String NODE_ADDRESS = "http://localhost:8545"; + private static final String HUB_ADDRESS = "0xC129e7917b7c7DeDfAa5Fff1FB18d5D7050fE8ca"; + private static final int BLOCK_TIME = 5; + private static final boolean IS_SIDECHAIN = true; + + ChainConfig chainConfig = ChainConfig.builder() + .id(ID) + .nodeAddress(NODE_ADDRESS) + .hubAddress(HUB_ADDRESS) + .blockTime(BLOCK_TIME) + .isSidechain(IS_SIDECHAIN) + .build(); - @InjectMocks PublicConfigurationController controller; @BeforeEach void init() { - MockitoAnnotations.openMocks(this); + controller = new PublicConfigurationController(chainConfig); } @Test void shouldReturnConfig() { - int blockTime = 5; PublicChainConfig expectedConfig = PublicChainConfig .builder() - .sidechain(true) - .chainId(65535) - .chainNodeUrl("http://localhost:8545") - .iexecHubContractAddress("0xC129e7917b7c7DeDfAa5Fff1FB18d5D7050fE8ca") - .blockTime(Duration.ofSeconds(blockTime)) + .sidechain(IS_SIDECHAIN) + .chainId(ID) + .chainNodeUrl(NODE_ADDRESS) + .iexecHubContractAddress(HUB_ADDRESS) + .blockTime(Duration.ofSeconds(BLOCK_TIME)) .build(); - when(chainConfig.getChainId()).thenReturn(expectedConfig.getChainId()); - when(chainConfig.isSidechain()).thenReturn(expectedConfig.isSidechain()); - when(chainConfig.getNodeAddress()).thenReturn(expectedConfig.getChainNodeUrl()); - when(chainConfig.getHubAddress()).thenReturn(expectedConfig.getIexecHubContractAddress()); - when(chainConfig.getBlockTime()).thenReturn(blockTime); ResponseEntity response = controller.getPublicChainConfig(); assertThat(response.getBody()) .isNotNull() diff --git a/src/test/java/com/iexec/blockchain/tool/ChainConfigTest.java b/src/test/java/com/iexec/blockchain/tool/ChainConfigTest.java index 84bb9e8e..65221f10 100644 --- a/src/test/java/com/iexec/blockchain/tool/ChainConfigTest.java +++ b/src/test/java/com/iexec/blockchain/tool/ChainConfigTest.java @@ -1,26 +1,31 @@ package com.iexec.blockchain.tool; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.Validator; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@Slf4j class ChainConfigTest { - final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); - private final static int DEFAULT_CHAIN_ID = 1; private final static String DEFAULT_NODE_ADDRESS = "http://localhost:8545"; private final static String DEFAULT_HUB_ADDRESS = "0xBF6B2B07e47326B7c8bfCb4A5460bef9f0Fd2002"; private static final int DEFAULT_BLOCK_TIME = 1; + private void validate(ChainConfig chainConfig) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method validateConfig = ChainConfig.class.getDeclaredMethod("validate"); + validateConfig.setAccessible(true); + validateConfig.invoke(chainConfig); + } + // region Valid data static Stream validData() { return Stream.of( @@ -36,21 +41,16 @@ static Stream validData() { void shouldValidate(Integer chainId, String nodeAddress, String hubAddress, - Integer blockTime) - throws NoSuchMethodException { - Method validateConfig = ChainConfig.class.getDeclaredMethod("validate"); - validateConfig.setAccessible(true); - + Integer blockTime) { final ChainConfig chainConfig = ChainConfig.builder() - .chainId(chainId) + .id(chainId) .nodeAddress(nodeAddress) .blockTime(blockTime) .hubAddress(hubAddress) - .validator(validator) .build(); - System.out.println(chainConfig); - assertThatCode(() -> validateConfig.invoke(chainConfig)) + log.info("{}", chainConfig); + assertThatCode(() -> validate(chainConfig)) .doesNotThrowAnyException(); } // endregion @@ -58,7 +58,6 @@ void shouldValidate(Integer chainId, // region Invalid chain ids static Stream invalidChainIds() { return Stream.of( - null, // Chain id should not be null 0, // Chain id should be strictly positive -1 // Chain id should be strictly positive ); @@ -66,23 +65,20 @@ static Stream invalidChainIds() { @ParameterizedTest @MethodSource("invalidChainIds") - void shouldNotValidateChainId(Integer chainId) throws NoSuchMethodException { - Method validateConfig = ChainConfig.class.getDeclaredMethod("validate"); - validateConfig.setAccessible(true); + void shouldNotValidateChainId(int chainId) { final ChainConfig chainConfig = ChainConfig.builder() - .chainId(chainId) + .id(chainId) .nodeAddress(DEFAULT_NODE_ADDRESS) .blockTime(DEFAULT_BLOCK_TIME) .hubAddress(DEFAULT_HUB_ADDRESS) - .validator(validator) .build(); - System.out.println(chainConfig); - assertThatThrownBy(() -> validateConfig.invoke(chainConfig)) + log.info("{}", chainConfig); + assertThatThrownBy(() -> validate(chainConfig)) .getRootCause() .isInstanceOf(ConstraintViolationException.class) - .hasMessageContaining("chainId") + .hasMessageContaining("Chain id should be positive") ; } // endregion @@ -99,20 +95,16 @@ static Stream invalidNodeAddresses() { @ParameterizedTest @MethodSource("invalidNodeAddresses") - void shouldNotValidateNodeAddress(String nodeAddress) throws NoSuchMethodException { - Method validateConfig = ChainConfig.class.getDeclaredMethod("validate"); - validateConfig.setAccessible(true); - + void shouldNotValidateNodeAddress(String nodeAddress) { final ChainConfig chainConfig = ChainConfig.builder() - .chainId(DEFAULT_CHAIN_ID) + .id(DEFAULT_CHAIN_ID) .nodeAddress(nodeAddress) .blockTime(DEFAULT_BLOCK_TIME) .hubAddress(DEFAULT_HUB_ADDRESS) - .validator(validator) .build(); - System.out.println(chainConfig); - assertThatThrownBy(() -> validateConfig.invoke(chainConfig)) + log.info("{}", chainConfig); + assertThatThrownBy(() -> validate(chainConfig)) .getRootCause() .isInstanceOf(ConstraintViolationException.class) .hasMessageContaining("nodeAddress") @@ -123,7 +115,6 @@ void shouldNotValidateNodeAddress(String nodeAddress) throws NoSuchMethodExcepti // region Invalid block time static Stream invalidBlockTimes() { return Stream.of( - null, // Block time should not be null 0, // Block time should be strictly positive -1 // Block time should be strictly positive ); @@ -131,20 +122,16 @@ static Stream invalidBlockTimes() { @ParameterizedTest @MethodSource("invalidBlockTimes") - void shouldNotValidateBlockTime(Integer blockTime) throws NoSuchMethodException { - Method validateConfig = ChainConfig.class.getDeclaredMethod("validate"); - validateConfig.setAccessible(true); - + void shouldNotValidateBlockTime(int blockTime) { final ChainConfig chainConfig = ChainConfig.builder() - .chainId(DEFAULT_CHAIN_ID) + .id(DEFAULT_CHAIN_ID) .nodeAddress(DEFAULT_NODE_ADDRESS) .blockTime(blockTime) .hubAddress(DEFAULT_HUB_ADDRESS) - .validator(validator) .build(); - System.out.println(chainConfig); - assertThatThrownBy(() -> validateConfig.invoke(chainConfig)) + log.info("{}", chainConfig); + assertThatThrownBy(() -> validate(chainConfig)) .getRootCause() .isInstanceOf(ConstraintViolationException.class) .hasMessageContaining("blockTime") @@ -166,19 +153,15 @@ static Stream invalidHubAddresses() { @ParameterizedTest @MethodSource("invalidHubAddresses") void shouldNotValidateHubAddress(String hubAddress) throws NoSuchMethodException { - Method validateConfig = ChainConfig.class.getDeclaredMethod("validate"); - validateConfig.setAccessible(true); - final ChainConfig chainConfig = ChainConfig.builder() - .chainId(DEFAULT_CHAIN_ID) + .id(DEFAULT_CHAIN_ID) .nodeAddress(DEFAULT_NODE_ADDRESS) .blockTime(DEFAULT_BLOCK_TIME) .hubAddress(hubAddress) - .validator(validator) .build(); - System.out.println(chainConfig); - assertThatThrownBy(() -> validateConfig.invoke(chainConfig)) + log.info("{}", chainConfig); + assertThatThrownBy(() -> validate(chainConfig)) .getRootCause() .isInstanceOf(ConstraintViolationException.class) .hasMessageContaining("hubAddress") diff --git a/src/test/java/com/iexec/blockchain/tool/Web3ServiceTests.java b/src/test/java/com/iexec/blockchain/tool/Web3jServiceTests.java similarity index 97% rename from src/test/java/com/iexec/blockchain/tool/Web3ServiceTests.java rename to src/test/java/com/iexec/blockchain/tool/Web3jServiceTests.java index 70cd4c5e..daf89b9c 100644 --- a/src/test/java/com/iexec/blockchain/tool/Web3ServiceTests.java +++ b/src/test/java/com/iexec/blockchain/tool/Web3jServiceTests.java @@ -23,7 +23,7 @@ class Web3jServiceTests { private final ChainConfig chainConfig = ChainConfig .builder() - .chainId(134) + .id(134) .isSidechain(true) .nodeAddress("https://bellecour.iex.ec") .hubAddress("0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f")