From 021c9c4396cbd39da3ec7e237c4612d57790d453 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Wed, 19 Jun 2024 17:48:16 +0200 Subject: [PATCH 01/19] Backmerge 8.5.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fef4b1c..5ba96b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. +## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/vNEXT) 2024 + ## [[8.5.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.5.0) 2024-06-19 ### Deprecation Notices From 5d0870871b99f26c7cf4a1f7c38defad5fd7051f Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Wed, 17 Jul 2024 11:54:28 +0200 Subject: [PATCH 02/19] Start scheduler in out-of-service mode --- CHANGELOG.md | 4 ++++ .../core/chain/BlockchainConnectionHealthIndicator.java | 7 ++++--- src/main/java/com/iexec/core/chain/DealWatcherService.java | 3 +-- .../java/com/iexec/core/chain/DealWatcherServiceTests.java | 1 + 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ba96b45..3ea3e916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/vNEXT) 2024 +### Bug Fixes + +- Start scheduler in out-of-service mode. (#712) + ## [[8.5.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.5.0) 2024-06-19 ### Deprecation Notices diff --git a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java index 6568274b..a519ca27 100644 --- a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java +++ b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH + * Copyright 2023-2024 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. @@ -60,7 +60,7 @@ public class BlockchainConnectionHealthIndicator implements HealthIndicator { @Getter private LocalDateTime firstFailure = null; @Getter - private boolean outOfService = false; + private boolean outOfService = true; /** * Required for test purposes. @@ -141,6 +141,7 @@ void checkConnection() { */ private void connectionFailed() { ++consecutiveFailures; + log.debug("connection failure [attempts:{}, threshold:{}]", consecutiveFailures, outOfServiceThreshold); if (consecutiveFailures >= outOfServiceThreshold) { log.error("Blockchain hasn't been accessed for a long period. " + "This Scheduler is now OUT-OF-SERVICE until communication is restored." + @@ -173,7 +174,7 @@ private void connectionFailed() { private void connectionSucceeded(long latestBlockNumber) { if (consecutiveFailures > 0) { log.info("Blockchain connection is now restored after a period of unavailability." + - " [block:{}, unavailabilityPeriod:{}]", + " [block:{}, unavailabilityPeriod:{}]", latestBlockNumber, pollingInterval.multipliedBy(consecutiveFailures)); firstFailure = null; consecutiveFailures = 0; diff --git a/src/main/java/com/iexec/core/chain/DealWatcherService.java b/src/main/java/com/iexec/core/chain/DealWatcherService.java index a03f6b2e..0be81c69 100644 --- a/src/main/java/com/iexec/core/chain/DealWatcherService.java +++ b/src/main/java/com/iexec/core/chain/DealWatcherService.java @@ -31,7 +31,6 @@ import io.reactivex.disposables.Disposable; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; @@ -108,7 +107,7 @@ public DealWatcherService(ChainConfig chainConfig, * a large number of tasks (BoT). */ @Async - @EventListener({ApplicationReadyEvent.class, ChainConnectedEvent.class}) + @EventListener(ChainConnectedEvent.class) public void run() { outOfService = false; disposeSubscription(dealEventsSubscription); diff --git a/src/test/java/com/iexec/core/chain/DealWatcherServiceTests.java b/src/test/java/com/iexec/core/chain/DealWatcherServiceTests.java index 78c94dd7..c575d4a8 100644 --- a/src/test/java/com/iexec/core/chain/DealWatcherServiceTests.java +++ b/src/test/java/com/iexec/core/chain/DealWatcherServiceTests.java @@ -310,6 +310,7 @@ void shouldNotUpdateLastSeenBlockWhenReceivingOldMissedDeal() { // region replayDealEvent @Test void shouldReplayAllEventInRange() { + ReflectionTestUtils.setField(dealWatcherService, "outOfService", false); BigInteger blockOfDeal = BigInteger.valueOf(3); IexecHubContract.SchedulerNoticeEventResponse schedulerNotice = createSchedulerNotice(blockOfDeal); From d740d7cc0a8170bca3f380d0849bea373c840496 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Wed, 17 Jul 2024 16:10:53 +0200 Subject: [PATCH 03/19] out-of-service check does not depende on previous consecutive failures --- .../core/chain/BlockchainConnectionHealthIndicator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java index a519ca27..5b702aa3 100644 --- a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java +++ b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java @@ -178,10 +178,10 @@ private void connectionSucceeded(long latestBlockNumber) { latestBlockNumber, pollingInterval.multipliedBy(consecutiveFailures)); firstFailure = null; consecutiveFailures = 0; - if (outOfService) { - outOfService = false; - applicationEventPublisher.publishEvent(new ChainConnectedEvent(this)); - } + } + if (outOfService) { + outOfService = false; + applicationEventPublisher.publishEvent(new ChainConnectedEvent(this)); } } From 2a313222d5f5d1eaec5e14c62e7c3757a572f569 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Wed, 17 Jul 2024 17:02:21 +0200 Subject: [PATCH 04/19] Add debug log events --- .../iexec/core/chain/BlockchainConnectionHealthIndicator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java index 5b702aa3..07f93680 100644 --- a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java +++ b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java @@ -147,6 +147,7 @@ private void connectionFailed() { "This Scheduler is now OUT-OF-SERVICE until communication is restored." + " [unavailabilityPeriod:{}]", pollingInterval.multipliedBy(outOfServiceThreshold)); if (!outOfService) { + log.debug("Publishing ChainDisconnectedEvent"); outOfService = true; applicationEventPublisher.publishEvent(new ChainDisconnectedEvent(this)); } @@ -180,6 +181,7 @@ private void connectionSucceeded(long latestBlockNumber) { consecutiveFailures = 0; } if (outOfService) { + log.debug("Publishing ChainConnectedEvent"); outOfService = false; applicationEventPublisher.publishEvent(new ChainConnectedEvent(this)); } From 65cfe7e9befd9d4ce3d0ec9c821497e7491fdcb1 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Wed, 17 Jul 2024 18:12:43 +0200 Subject: [PATCH 05/19] Replace `PostConstruct` with `@EventListener` in BlockchainConnectionHealthIndicator --- .../core/chain/BlockchainConnectionHealthIndicator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java index 07f93680..cb30bc31 100644 --- a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java +++ b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java @@ -25,10 +25,11 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.time.Clock; import java.time.Duration; import java.time.LocalDateTime; @@ -101,7 +102,7 @@ public BlockchainConnectionHealthIndicator( this.clock = clock; } - @PostConstruct + @EventListener(ApplicationReadyEvent.class) void scheduleMonitoring() { monitoringExecutor.scheduleAtFixedRate(this::checkConnection, 0, pollingInterval.toSeconds(), TimeUnit.SECONDS); } From e31271facfb7c1fe0c701f5d8563ebd7b9040108 Mon Sep 17 00:00:00 2001 From: Mikexec <129768894+Mikexec@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:41:55 +0200 Subject: [PATCH 06/19] Upgrade to eclipse-temurin 11.0.24_8 (#713) --- CHANGELOG.md | 4 ++++ Dockerfile | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea3e916..5e40ef9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/vNEXT) 2024 +### Dependency Upgrades + +- Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#713) + ### Bug Fixes - Start scheduler in out-of-service mode. (#712) diff --git a/Dockerfile b/Dockerfile index d919e9c4..48cf2b55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:11.0.22_7-jre-focal +FROM eclipse-temurin:11.0.24_8-jre-focal ARG jar From ac0ac01d206755bb577df12e9850f11830a00bf4 Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:11:33 +0200 Subject: [PATCH 07/19] Upgrade to Gradle 8.10.2 (#714) --- CHANGELOG.md | 1 + build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +++++-- gradlew.bat | 2 ++ 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e40ef9f..8162d3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Dependency Upgrades - Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#713) +- Upgrade to Gradle 8.10.2. (#714) ### Bug Fixes diff --git a/build.gradle b/build.gradle index 138986fa..7a6de86b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ plugins { id 'java' - id 'io.freefair.lombok' version '8.6' + id 'io.freefair.lombok' version '8.10.2' id 'org.springframework.boot' version '2.7.18' - id 'io.spring.dependency-management' version '1.1.4' + id 'io.spring.dependency-management' version '1.1.6' id 'jacoco' - id 'org.sonarqube' version '5.0.0.4638' + id 'org.sonarqube' version '5.1.0.4882' id 'maven-publish' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 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 HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a..df97d72b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From fb44c5ccc5900c53cd166e3b26834eab636c14a7 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Mon, 28 Oct 2024 11:18:49 +0100 Subject: [PATCH 08/19] Scheduler needs to enter out-of-service mode on first blockchain communication loss (#715) --- CHANGELOG.md | 10 ++- README.md | 2 - .../BlockchainConnectionHealthIndicator.java | 80 +++++++------------ src/main/resources/application.yml | 3 - ...ckchainConnectionHealthIndicatorTests.java | 71 +++++++--------- 5 files changed, 64 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8162d3fc..17626573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,17 @@ All notable changes to this project will be documented in this file. ## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/vNEXT) 2024 +### Bug Fixes + +- Start scheduler in out-of-service mode. (#712) +- Scheduler needs to enter out-of-service mode on first blockchain communication loss. + This is due to **Nethermind v1.14.7+99775bf7** where filters are lost on restart. (#715) + ### Dependency Upgrades - Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#713) - Upgrade to Gradle 8.10.2. (#714) -### Bug Fixes - -- Start scheduler in out-of-service mode. (#712) - ## [[8.5.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.5.0) 2024-06-19 ### Deprecation Notices diff --git a/README.md b/README.md index 36299331..b79fe28c 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,6 @@ You can configure the _iExec Core Scheduler_ with the following properties: | `IEXEC_CONFIG_SERVER_PROTOCOL` | _iExec Config Server_ communication protocol. | String | `http` | | `IEXEC_CONFIG_SERVER_HOST` | _iExec Config Server_ host. | String | `localhost` | | `IEXEC_CONFIG_SERVER_PORT` | _iExec Config Server_ port. | Positive integer | `8888` | -| `IEXEC_CHAIN_HEALTH_POLLING_INTERVAL_IN_BLOCKS` | Polling interval (in blocks) on the blockchain to check this _Scheduler_ can communicate with it. | Positive integer | 3 | -| `IEXEC_CHAIN_HEALTH_OUT_OF_SERVICE_THRESHOLD` | Max number of consecutive failures of blockchain connection attempts before this _Scheduler_ is declared as OUT-OF-SERVICE. | Positive integer | 4 | | `IEXEC_RESULT_REPOSITORY_PROTOCOL` | _iExec Result Proxy_ server communication protocol. | String | `http` | | `IEXEC_RESULT_REPOSITORY_HOST` | _iExec Result Proxy_ server host. | String | `localhost` | | `IEXEC_RESULT_REPOSITORY_PORT` | _iExec Result Proxy_ server port. | Positive integer | `13200` | diff --git a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java index cb30bc31..b46fb84b 100644 --- a/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java +++ b/src/main/java/com/iexec/core/chain/BlockchainConnectionHealthIndicator.java @@ -21,7 +21,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; @@ -50,18 +49,14 @@ public class BlockchainConnectionHealthIndicator implements HealthIndicator { /** * Number of consecutive failures before declaring this Scheduler is out-of-service. */ - private final int outOfServiceThreshold; private final ScheduledExecutorService monitoringExecutor; /** - * Current number of consecutive failures. + * Current number of consecutive failures. Startup value is greater than {@literal 0} to be out-of-service. */ - @Getter - private int consecutiveFailures = 0; + private int consecutiveFailures = 1; @Getter private LocalDateTime firstFailure = null; - @Getter - private boolean outOfService = true; /** * Required for test purposes. @@ -72,15 +67,11 @@ public class BlockchainConnectionHealthIndicator implements HealthIndicator { public BlockchainConnectionHealthIndicator( ApplicationEventPublisher applicationEventPublisher, Web3jService web3jService, - ChainConfig chainConfig, - @Value("${chain.health.pollingIntervalInBlocks}") int pollingIntervalInBlocks, - @Value("${chain.health.outOfServiceThreshold}") int outOfServiceThreshold) { + ChainConfig chainConfig) { this( applicationEventPublisher, web3jService, chainConfig, - pollingIntervalInBlocks, - outOfServiceThreshold, Executors.newSingleThreadScheduledExecutor(), Clock.systemDefaultZone() ); @@ -90,14 +81,11 @@ public BlockchainConnectionHealthIndicator( ApplicationEventPublisher applicationEventPublisher, Web3jService web3jService, ChainConfig chainConfig, - int pollingIntervalInBlocks, - int outOfServiceThreshold, ScheduledExecutorService monitoringExecutor, Clock clock) { this.applicationEventPublisher = applicationEventPublisher; this.web3jService = web3jService; - this.pollingInterval = chainConfig.getBlockTime().multipliedBy(pollingIntervalInBlocks); - this.outOfServiceThreshold = outOfServiceThreshold; + this.pollingInterval = chainConfig.getBlockTime(); this.monitoringExecutor = monitoringExecutor; this.clock = clock; } @@ -110,8 +98,7 @@ void scheduleMonitoring() { /** * Check blockchain is reachable by retrieving the latest block number. *

- * If it isn't, then increment {@link BlockchainConnectionHealthIndicator#consecutiveFailures} counter. - * If counter reaches {@link BlockchainConnectionHealthIndicator#outOfServiceThreshold}, + * If it isn't, then increment {@link BlockchainConnectionHealthIndicator#consecutiveFailures} counter, * then this Health Indicator becomes {@link Status#OUT_OF_SERVICE}. *

* If blockchain is reachable, then reset the counter. @@ -132,34 +119,20 @@ void checkConnection() { /** * Increment the {@link BlockchainConnectionHealthIndicator#consecutiveFailures} counter. *

- * If first failure, set the {@link BlockchainConnectionHealthIndicator#firstFailure} to current time. - *

- * If {@link BlockchainConnectionHealthIndicator#outOfServiceThreshold} has been reached for the first time: + * If first failure: *

    - *
  • Set {@link BlockchainConnectionHealthIndicator#outOfService} to {@literal true} - *
  • Publish a {@link ChainDisconnectedEvent} event. + *
  • Publish a {@link ChainDisconnectedEvent} event + *
  • Set the {@link BlockchainConnectionHealthIndicator#firstFailure} to current time *
*/ private void connectionFailed() { - ++consecutiveFailures; - log.debug("connection failure [attempts:{}, threshold:{}]", consecutiveFailures, outOfServiceThreshold); - if (consecutiveFailures >= outOfServiceThreshold) { - log.error("Blockchain hasn't been accessed for a long period. " + - "This Scheduler is now OUT-OF-SERVICE until communication is restored." + - " [unavailabilityPeriod:{}]", pollingInterval.multipliedBy(outOfServiceThreshold)); - if (!outOfService) { - log.debug("Publishing ChainDisconnectedEvent"); - outOfService = true; - applicationEventPublisher.publishEvent(new ChainDisconnectedEvent(this)); - } - } else { - if (consecutiveFailures == 1) { - firstFailure = LocalDateTime.now(clock); - } - log.warn("Blockchain is unavailable. Will retry connection." + - " [unavailabilityPeriod:{}, nextRetry:{}]", - pollingInterval.multipliedBy(consecutiveFailures), pollingInterval); + if (!isOutOfService()) { + log.error("Blockchain communication failed, this Scheduler is now OUT-OF-SERVICE until communication is restored."); + log.debug("Publishing ChainDisconnectedEvent"); + applicationEventPublisher.publishEvent(new ChainDisconnectedEvent(this)); + firstFailure = LocalDateTime.now(clock); } + ++consecutiveFailures; } /** @@ -169,28 +142,24 @@ private void connectionFailed() { *
    *
  • Log a "connection restored" message. *
  • Reset the {@link BlockchainConnectionHealthIndicator#firstFailure} var to {@code null} - *
  • If {@link Status#OUT_OF_SERVICE}, publish a {@link ChainConnectedEvent} event and reset the - * {@link BlockchainConnectionHealthIndicator#outOfService} state + *
  • If {@link Status#OUT_OF_SERVICE}, publish a {@link ChainConnectedEvent} event *
*/ private void connectionSucceeded(long latestBlockNumber) { - if (consecutiveFailures > 0) { + if (isOutOfService()) { log.info("Blockchain connection is now restored after a period of unavailability." + " [block:{}, unavailabilityPeriod:{}]", latestBlockNumber, pollingInterval.multipliedBy(consecutiveFailures)); firstFailure = null; consecutiveFailures = 0; - } - if (outOfService) { log.debug("Publishing ChainConnectedEvent"); - outOfService = false; applicationEventPublisher.publishEvent(new ChainConnectedEvent(this)); } } @Override public Health health() { - final Health.Builder healthBuilder = outOfService + final Health.Builder healthBuilder = isOutOfService() ? Health.outOfService() : Health.up(); @@ -201,10 +170,23 @@ public Health health() { return healthBuilder .withDetail("consecutiveFailures", consecutiveFailures) .withDetail("pollingInterval", pollingInterval) - .withDetail("outOfServiceThreshold", outOfServiceThreshold) .build(); } + /** + * Returns whether the scheduler should be considered out-of-service. + * + * @return {@literal true} in case of at least one consecutive failures, {@literal false} otherwise. + */ + public boolean isOutOfService() { + return consecutiveFailures > 0; + } + + /** + * Returns whether the scheduler is up or not. + * + * @return {@literal true} if the scheduler is up, {@literal false} otherwise. + */ public boolean isUp() { return health().getStatus() == Status.UP; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 875a477d..0caea3c3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -56,9 +56,6 @@ chain: startBlockNumber: ${IEXEC_START_BLOCK_NUMBER:0} gasPriceMultiplier: ${IEXEC_GAS_PRICE_MULTIPLIER:1.0} # txs will be sent with networkGasPrice*gasPriceMultiplier, 4.0 means super fast gasPriceCap: ${IEXEC_GAS_PRICE_CAP:22000000000} #in Wei, will be used for txs if networkGasPrice*gasPriceMultiplier > gasPriceCap - health: - pollingIntervalInBlocks: ${IEXEC_CHAIN_HEALTH_POLLING_INTERVAL_IN_BLOCKS:3} - outOfServiceThreshold: ${IEXEC_CHAIN_HEALTH_OUT_OF_SERVICE_THRESHOLD:4} blockchain-adapter: protocol: ${IEXEC_CORE_CHAIN_ADAPTER_PROTOCOL:http} diff --git a/src/test/java/com/iexec/core/chain/BlockchainConnectionHealthIndicatorTests.java b/src/test/java/com/iexec/core/chain/BlockchainConnectionHealthIndicatorTests.java index 525e4431..deb0e779 100644 --- a/src/test/java/com/iexec/core/chain/BlockchainConnectionHealthIndicatorTests.java +++ b/src/test/java/com/iexec/core/chain/BlockchainConnectionHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH + * Copyright 2023-2024 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. @@ -19,12 +19,13 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.actuate.health.Health; import org.springframework.context.ApplicationEventPublisher; import org.springframework.test.util.ReflectionTestUtils; @@ -39,8 +40,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class BlockchainConnectionHealthIndicatorTests { - private static final int POLLING_INTERVAL_IN_BLOCKS = 3; private static final int OUT_OF_SERVICE_THRESHOLD = 4; private static final Duration BLOCK_TIME = Duration.ofSeconds(5); private static final Clock CLOCK = Clock.fixed(Instant.ofEpochSecond(1), ZoneId.systemDefault()); @@ -58,15 +59,12 @@ class BlockchainConnectionHealthIndicatorTests { @BeforeEach void init() { - MockitoAnnotations.openMocks(this); when(chainConfig.getBlockTime()).thenReturn(BLOCK_TIME); this.blockchainConnectionHealthIndicator = new BlockchainConnectionHealthIndicator( applicationEventPublisher, web3jService, chainConfig, - POLLING_INTERVAL_IN_BLOCKS, - OUT_OF_SERVICE_THRESHOLD, executor, CLOCK ); @@ -80,7 +78,7 @@ void shouldScheduleMonitoring() { Mockito.verify(executor).scheduleAtFixedRate( any(), eq(0L), - eq(15L), + eq(5L), eq(TimeUnit.SECONDS) ); } @@ -103,52 +101,47 @@ void shouldScheduleMonitoring() { */ static Stream checkConnectionParameters() { return Stream.of( - // Should get latest block number and reset `firstFailure` - Arguments.of(0, false, null , 1L , 0, false, null), - Arguments.of(0, false, null , 5L , 0, false, null), - Arguments.of(0, false, null , 100L , 0, false, null), - Arguments.of(0, false, null , 5_000L, 0, false, null), - Arguments.of(1, false, LocalDateTime.now(CLOCK), 1L , 0, false, null), - - // Should not get latest block number and increment consecutive failures (but stays UP) - Arguments.of(0, false, null , 0L, 1, false, LocalDateTime.now(CLOCK)), - Arguments.of(1, false, LocalDateTime.now(CLOCK), 0L, 2, false, LocalDateTime.now(CLOCK)), - Arguments.of(2, false, LocalDateTime.now(CLOCK), 0L, 3, false, LocalDateTime.now(CLOCK)), + // Should get latest block number and stay OUT-OF-SERVICE + Arguments.of(0, null, 1L, false, null), + Arguments.of(0, null, 5L, false, null), + Arguments.of(0, null, 100L, false, null), + Arguments.of(0, null, 5_000L, false, null), // Should not get latest block number and become OUT-OF-SERVICE - Arguments.of(3 , false, LocalDateTime.now(CLOCK), 0L, 4 , true, LocalDateTime.now(CLOCK)), - Arguments.of(4 , true , LocalDateTime.now(CLOCK), 0L, 5 , true, LocalDateTime.now(CLOCK)), - Arguments.of(50, true , LocalDateTime.now(CLOCK), 0L, 51, true, LocalDateTime.now(CLOCK)), + // This will set firstFailure + Arguments.of(0, null, 0L, true, LocalDateTime.now(CLOCK)), + Arguments.of(1, LocalDateTime.now(CLOCK), 0L, true, LocalDateTime.now(CLOCK)), + Arguments.of(2, LocalDateTime.now(CLOCK), 0L, true, LocalDateTime.now(CLOCK)), + Arguments.of(3, LocalDateTime.now(CLOCK), 0L, true, LocalDateTime.now(CLOCK)), + Arguments.of(4, LocalDateTime.now(CLOCK), 0L, true, LocalDateTime.now(CLOCK)), + Arguments.of(50, LocalDateTime.now(CLOCK), 0L, true, LocalDateTime.now(CLOCK)), // Should get latest block number and exit OUT-OF-SERVICE - Arguments.of(4 , true, LocalDateTime.now(CLOCK), 1L, 0, false, null), - Arguments.of(5 , true, LocalDateTime.now(CLOCK), 1L, 0, false, null), - Arguments.of(50, true, LocalDateTime.now(CLOCK), 1L, 0, false, null) + // This will reset firstFailure and consecutiveFailures + Arguments.of(1, LocalDateTime.now(CLOCK), 1L, false, null), + Arguments.of(4, LocalDateTime.now(CLOCK), 1L, false, null), + Arguments.of(5, LocalDateTime.now(CLOCK), 1L, false, null), + Arguments.of(50, LocalDateTime.now(CLOCK), 1L, false, null) ); } @ParameterizedTest @MethodSource("checkConnectionParameters") void checkConnection(int previousConsecutiveFailures, - boolean previousOutOfService, LocalDateTime previousFirstFailure, long latestBlockNumber, - int expectedConsecutiveFailures, boolean expectedOutOfService, LocalDateTime expectedFirstFailure) { setConsecutiveFailures(previousConsecutiveFailures); - setOufOService(previousOutOfService); setFirstFailure(previousFirstFailure); when(web3jService.getLatestBlockNumber()).thenReturn(latestBlockNumber); blockchainConnectionHealthIndicator.checkConnection(); - final Integer consecutiveFailures = blockchainConnectionHealthIndicator.getConsecutiveFailures(); - final Boolean outOfService = blockchainConnectionHealthIndicator.isOutOfService(); + final boolean outOfService = blockchainConnectionHealthIndicator.isOutOfService(); final LocalDateTime firstFailure = blockchainConnectionHealthIndicator.getFirstFailure(); - Assertions.assertThat(consecutiveFailures).isEqualTo(expectedConsecutiveFailures); Assertions.assertThat(outOfService).isEqualTo(expectedOutOfService); Assertions.assertThat(firstFailure).isEqualTo(expectedFirstFailure); @@ -161,14 +154,12 @@ void checkConnection(int previousConsecutiveFailures, void shouldReturnOutOfService() { final LocalDateTime firstFailure = LocalDateTime.now(CLOCK); - setOufOService(true); setConsecutiveFailures(OUT_OF_SERVICE_THRESHOLD); setFirstFailure(firstFailure); final Health expectedHealth = Health.outOfService() .withDetail("consecutiveFailures", OUT_OF_SERVICE_THRESHOLD) - .withDetail("pollingInterval", Duration.ofSeconds(15)) - .withDetail("outOfServiceThreshold", OUT_OF_SERVICE_THRESHOLD) + .withDetail("pollingInterval", Duration.ofSeconds(5)) .withDetail("firstFailure", firstFailure) .build(); @@ -178,14 +169,12 @@ void shouldReturnOutOfService() { @Test void shouldReturnUpAndNoFirstFailure() { - setOufOService(false); setConsecutiveFailures(0); setFirstFailure(null); final Health expectedHealth = Health.up() .withDetail("consecutiveFailures", 0) - .withDetail("pollingInterval", Duration.ofSeconds(15)) - .withDetail("outOfServiceThreshold", OUT_OF_SERVICE_THRESHOLD) + .withDetail("pollingInterval", Duration.ofSeconds(5)) .build(); final Health health = blockchainConnectionHealthIndicator.health(); @@ -196,14 +185,12 @@ void shouldReturnUpAndNoFirstFailure() { void shouldReturnUpButWithFirstFailure() { final LocalDateTime firstFailure = LocalDateTime.now(CLOCK); - setOufOService(false); setConsecutiveFailures(1); setFirstFailure(firstFailure); - final Health expectedHealth = Health.up() + final Health expectedHealth = Health.outOfService() .withDetail("consecutiveFailures", 1) - .withDetail("pollingInterval", Duration.ofSeconds(15)) - .withDetail("outOfServiceThreshold", OUT_OF_SERVICE_THRESHOLD) + .withDetail("pollingInterval", Duration.ofSeconds(5)) .withDetail("firstFailure", firstFailure) .build(); @@ -213,10 +200,6 @@ void shouldReturnUpButWithFirstFailure() { // endregion // region utils - private void setOufOService(boolean isOutOfService) { - ReflectionTestUtils.setField(blockchainConnectionHealthIndicator, "outOfService", isOutOfService); - } - private void setConsecutiveFailures(int consecutiveFailures) { ReflectionTestUtils.setField(blockchainConnectionHealthIndicator, "consecutiveFailures", consecutiveFailures); } From c7f32dba5b6f5206b22036188fd9c0649432f20d Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:28:37 +0100 Subject: [PATCH 09/19] Fix result-proxy URL resolution (#716) --- CHANGELOG.md | 1 + .../ResultRepositoryConfiguration.java | 13 ++- .../core/replicate/ReplicatesService.java | 2 +- .../com/iexec/core/result/ResultService.java | 16 ++-- .../ResultRepositoryConfigurationTest.java | 81 +++++++++++++++++++ .../core/replicate/ReplicateServiceTests.java | 19 +++-- .../iexec/core/replicate/ReplicateTests.java | 5 +- .../iexec/core/result/ResultServiceTests.java | 23 ++++-- 8 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 src/test/java/com/iexec/core/configuration/ResultRepositoryConfigurationTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 17626573..5d641b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Start scheduler in out-of-service mode. (#712) - Scheduler needs to enter out-of-service mode on first blockchain communication loss. This is due to **Nethermind v1.14.7+99775bf7** where filters are lost on restart. (#715) +- Use Result Proxy URL defined in deal parameters if any, fall back to scheduler default one otherwise. (#716) ### Dependency Upgrades diff --git a/src/main/java/com/iexec/core/configuration/ResultRepositoryConfiguration.java b/src/main/java/com/iexec/core/configuration/ResultRepositoryConfiguration.java index 2b446bda..fcf2fa14 100644 --- a/src/main/java/com/iexec/core/configuration/ResultRepositoryConfiguration.java +++ b/src/main/java/com/iexec/core/configuration/ResultRepositoryConfiguration.java @@ -20,13 +20,15 @@ import com.iexec.resultproxy.api.ResultProxyClientBuilder; import feign.Logger; import lombok.Value; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; -import org.springframework.context.annotation.Bean; @Value @ConstructorBinding @ConfigurationProperties(prefix = "result-repository") +@Slf4j public class ResultRepositoryConfiguration { String protocol; String host; @@ -36,8 +38,11 @@ public String getResultRepositoryURL() { return protocol + "://" + host + ":" + port; } - @Bean - public ResultProxyClient resultProxyClient() { - return ResultProxyClientBuilder.getInstance(Logger.Level.NONE, getResultRepositoryURL()); + public ResultProxyClient createResultProxyClientFromURL(final String url) { + final boolean useDefaultUrl = StringUtils.isBlank(url); + final String resultProxyClientURL = useDefaultUrl ? getResultRepositoryURL() : url; + log.debug("result-proxy URL [url:{}, default-url:{}]", resultProxyClientURL, useDefaultUrl); + return ResultProxyClientBuilder.getInstance(Logger.Level.NONE, resultProxyClientURL); } + } diff --git a/src/main/java/com/iexec/core/replicate/ReplicatesService.java b/src/main/java/com/iexec/core/replicate/ReplicatesService.java index bf4fc3e9..0725e42b 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicatesService.java +++ b/src/main/java/com/iexec/core/replicate/ReplicatesService.java @@ -554,7 +554,7 @@ public boolean isResultUploaded(TaskDescription task) { } // Cloud computing, upload to IPFS - basic & TEE - return resultService.isResultUploaded(task.getChainTaskId()); + return resultService.isResultUploaded(task); } public boolean didReplicateContributeOnchain(String chainTaskId, String walletAddress) { diff --git a/src/main/java/com/iexec/core/result/ResultService.java b/src/main/java/com/iexec/core/result/ResultService.java index d416e925..722db2d1 100644 --- a/src/main/java/com/iexec/core/result/ResultService.java +++ b/src/main/java/com/iexec/core/result/ResultService.java @@ -17,7 +17,9 @@ package com.iexec.core.result; import com.iexec.commons.poco.chain.WorkerpoolAuthorization; +import com.iexec.commons.poco.task.TaskDescription; import com.iexec.core.chain.SignatureService; +import com.iexec.core.configuration.ResultRepositoryConfiguration; import com.iexec.core.task.Task; import com.iexec.core.task.TaskService; import com.iexec.resultproxy.api.ResultProxyClient; @@ -33,18 +35,20 @@ @Service public class ResultService { - private final ResultProxyClient resultProxyClient; + private final ResultRepositoryConfiguration resultRepositoryConfiguration; private final SignatureService signatureService; private final TaskService taskService; - public ResultService(final ResultProxyClient resultProxyClient, final SignatureService signatureService, final TaskService taskService) { - this.resultProxyClient = resultProxyClient; + public ResultService(final ResultRepositoryConfiguration resultRepositoryConfiguration, final SignatureService signatureService, final TaskService taskService) { + this.resultRepositoryConfiguration = resultRepositoryConfiguration; this.signatureService = signatureService; this.taskService = taskService; } @Retryable(value = FeignException.class) - public boolean isResultUploaded(final String chainTaskId) { + public boolean isResultUploaded(final TaskDescription taskDescription) { + final String chainTaskId = taskDescription.getChainTaskId(); + final ResultProxyClient resultProxyClient = resultRepositoryConfiguration.createResultProxyClientFromURL(taskDescription.getResultStorageProxy()); final String enclaveChallenge = taskService.getTaskByChainTaskId(chainTaskId).map(Task::getEnclaveChallenge).orElse(EMPTY_ADDRESS); final WorkerpoolAuthorization workerpoolAuthorization = signatureService.createAuthorization(signatureService.getAddress(), chainTaskId, enclaveChallenge); final String resultProxyToken = resultProxyClient.getJwt(workerpoolAuthorization.getSignature().getValue(), workerpoolAuthorization); @@ -58,8 +62,8 @@ public boolean isResultUploaded(final String chainTaskId) { } @Recover - private boolean isResultUploaded(final FeignException e, final String chainTaskId) { - log.error("Cannot check isResultUploaded after multiple retries [chainTaskId:{}]", chainTaskId, e); + private boolean isResultUploaded(final FeignException e, final TaskDescription taskDescription) { + log.error("Cannot check isResultUploaded after multiple retries [chainTaskId:{}]", taskDescription.getChainTaskId(), e); return false; } } diff --git a/src/test/java/com/iexec/core/configuration/ResultRepositoryConfigurationTest.java b/src/test/java/com/iexec/core/configuration/ResultRepositoryConfigurationTest.java new file mode 100644 index 00000000..9168e0a5 --- /dev/null +++ b/src/test/java/com/iexec/core/configuration/ResultRepositoryConfigurationTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 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. + * 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 com.iexec.core.configuration; + +import com.iexec.resultproxy.api.ResultProxyClient; +import com.iexec.resultproxy.api.ResultProxyClientBuilder; +import feign.Logger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ResultRepositoryConfigurationTest { + + private final ResultRepositoryConfiguration config = new ResultRepositoryConfiguration(PROTOCOL, HOST, PORT); + + private static final String PROTOCOL = "http"; + private static final String HOST = "localhost"; + private static final String PORT = "8080"; + + @Test + void shouldReturnCorrectResultRepositoryURL() { + String expectedUrl = "http://localhost:8080"; + assertEquals(expectedUrl, config.getResultRepositoryURL()); + } + + @Test + void shouldCreateResultProxyClientWithDefaultUrlWhenProxyUrlIsNullOrEmpty() { + try (MockedStatic mockedBuilder = mockStatic(ResultProxyClientBuilder.class)) { + ResultProxyClient mockClient = mock(ResultProxyClient.class); + String defaultUrl = config.getResultRepositoryURL(); + + mockedBuilder.when(() -> ResultProxyClientBuilder.getInstance(eq(Logger.Level.NONE), eq(defaultUrl))) + .thenReturn(mockClient); + + ResultProxyClient clientWithNull = config.createResultProxyClientFromURL(null); + assertNotNull(clientWithNull); + + ResultProxyClient clientWithEmpty = config.createResultProxyClientFromURL(""); + assertNotNull(clientWithEmpty); + + mockedBuilder.verify(() -> ResultProxyClientBuilder.getInstance(Logger.Level.NONE, defaultUrl), times(2)); + } + } + + @Test + void shouldCreateResultProxyClientWithProvidedProxyUrl() { + try (MockedStatic mockedBuilder = mockStatic(ResultProxyClientBuilder.class)) { + ResultProxyClient mockClient = mock(ResultProxyClient.class); + String proxyUrl = "http://proxy.example.com"; + + mockedBuilder.when(() -> ResultProxyClientBuilder.getInstance(eq(Logger.Level.NONE), eq(proxyUrl))) + .thenReturn(mockClient); + + ResultProxyClient client = config.createResultProxyClientFromURL(proxyUrl); + assertNotNull(client); + + mockedBuilder.verify(() -> ResultProxyClientBuilder.getInstance(Logger.Level.NONE, proxyUrl)); + } + } +} diff --git a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java index 24a4832f..47544961 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java @@ -760,11 +760,11 @@ void shouldCheckResultServiceAndReturnTrue(boolean isTeeTask) { .build(); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)) .thenReturn(taskDescription); - when(resultService.isResultUploaded(CHAIN_TASK_ID)).thenReturn(true); + when(resultService.isResultUploaded(taskDescription)).thenReturn(true); boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isTrue(); - verify(resultService).isResultUploaded(CHAIN_TASK_ID); + verify(resultService).isResultUploaded(taskDescription); } @ParameterizedTest @@ -778,11 +778,11 @@ void shouldCheckResultServiceAndReturnFalse(boolean isTeeTask) { .build(); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)) .thenReturn(taskDescription); - when(resultService.isResultUploaded(CHAIN_TASK_ID)).thenReturn(false); + when(resultService.isResultUploaded(taskDescription)).thenReturn(false); boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isFalse(); - verify(resultService).isResultUploaded(CHAIN_TASK_ID); + verify(resultService).isResultUploaded(taskDescription); } @Test @@ -792,7 +792,7 @@ void shouldReturnFalseSinceTaskNotFound() { boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isFalse(); - verify(resultService, never()).isResultUploaded(CHAIN_TASK_ID); + verifyNoInteractions(resultService); } @ParameterizedTest @@ -808,7 +808,7 @@ void shouldReturnTrueForCallbackTask(boolean isTeeTask) { boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isTrue(); - verify(resultService, never()).isResultUploaded(CHAIN_TASK_ID); + verifyNoInteractions(resultService); } @Test @@ -1111,7 +1111,6 @@ void shouldNotAuthorizeUpdateOnResultUploadedSinceNoResultLink() { .taskDescription(TaskDescription.builder().chainTaskId(CHAIN_TASK_ID).build()) .build(); - when(resultService.isResultUploaded(CHAIN_TASK_ID)).thenReturn(true); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, updateArgs)) .isEqualTo(ReplicateStatusUpdateError.GENERIC_CANT_UPDATE); @@ -1170,7 +1169,7 @@ void shouldAuthorizeUpdateOnContributeAndFinalizeDone() { .thenReturn(true); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)).thenReturn(task); when(iexecHubService.isTaskInCompletedStatusOnChain(CHAIN_TASK_ID)).thenReturn(true); - when(resultService.isResultUploaded(CHAIN_TASK_ID)).thenReturn(true); + when(resultService.isResultUploaded(task)).thenReturn(true); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, null)) .isEqualTo(ReplicateStatusUpdateError.NO_ERROR); @@ -1212,14 +1211,14 @@ void shouldNotAuthorizeUpdateOnContributeAndFinalizeDoneWhenNotUploaded() { when(iexecHubService.repeatIsRevealedTrue(CHAIN_TASK_ID, WALLET_WORKER_1)) .thenReturn(true); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)).thenReturn(task); - when(resultService.isResultUploaded(CHAIN_TASK_ID)).thenReturn(false); + when(resultService.isResultUploaded(task)).thenReturn(false); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, null)) .isEqualTo(ReplicateStatusUpdateError.GENERIC_CANT_UPDATE); verify(iexecHubService).repeatIsRevealedTrue(CHAIN_TASK_ID, WALLET_WORKER_1); verify(iexecHubService).getTaskDescription(CHAIN_TASK_ID); - verify(resultService).isResultUploaded(CHAIN_TASK_ID); + verify(resultService).isResultUploaded(task); } @Test diff --git a/src/test/java/com/iexec/core/replicate/ReplicateTests.java b/src/test/java/com/iexec/core/replicate/ReplicateTests.java index dec41b7a..b8e789b6 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateTests.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.iexec.common.replicate.ReplicateStatus; import com.iexec.common.replicate.ReplicateStatusModifier; import com.iexec.common.replicate.ReplicateStatusUpdate; @@ -60,7 +61,9 @@ void shouldSerializeAndDeserializeReplicate() throws JsonProcessingException { + "\"busyComputing\":true," + "\"recoverable\":true" + "}"; - assertThat(jsonString).isEqualTo(expectedString); + ObjectNode actualJsonNode = (ObjectNode) mapper.readTree(jsonString); + ObjectNode expectedJsonNode = (ObjectNode) mapper.readTree(expectedString); + assertThat(actualJsonNode).isEqualTo(expectedJsonNode); Replicate deserializedReplicate = mapper.readValue(jsonString, Replicate.class); assertThat(deserializedReplicate).usingRecursiveComparison().isEqualTo(replicate); } diff --git a/src/test/java/com/iexec/core/result/ResultServiceTests.java b/src/test/java/com/iexec/core/result/ResultServiceTests.java index 283417d8..cf4137db 100644 --- a/src/test/java/com/iexec/core/result/ResultServiceTests.java +++ b/src/test/java/com/iexec/core/result/ResultServiceTests.java @@ -18,9 +18,11 @@ import com.iexec.commons.poco.chain.WorkerpoolAuthorization; import com.iexec.commons.poco.security.Signature; +import com.iexec.commons.poco.task.TaskDescription; import com.iexec.commons.poco.utils.BytesUtils; import com.iexec.commons.poco.utils.HashUtils; import com.iexec.core.chain.SignatureService; +import com.iexec.core.configuration.ResultRepositoryConfiguration; import com.iexec.core.task.Task; import com.iexec.core.task.TaskService; import com.iexec.resultproxy.api.ResultProxyClient; @@ -28,15 +30,17 @@ import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.web3j.crypto.Credentials; import org.web3j.crypto.Keys; import org.web3j.crypto.Sign; import java.util.Optional; +import static com.iexec.commons.poco.chain.DealParams.IPFS_RESULT_STORAGE_PROVIDER; import static com.iexec.commons.poco.tee.TeeUtils.TEE_SCONE_ONLY_TAG; import static com.iexec.commons.poco.utils.BytesUtils.EMPTY_ADDRESS; import static com.iexec.core.task.TaskTestsUtils.CHAIN_TASK_ID; @@ -48,10 +52,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class ResultServiceTests { @Mock private ResultProxyClient resultProxyClient; @Mock + private ResultRepositoryConfiguration resultRepositoryConfiguration; + @Mock private SignatureService signatureService; @Mock private TaskService taskService; @@ -63,10 +70,13 @@ class ResultServiceTests { private Credentials schedulerCreds; private Signature signature; private WorkerpoolAuthorization workerpoolAuthorization; + private final TaskDescription taskDescription = TaskDescription.builder() + .chainTaskId(CHAIN_TASK_ID) + .resultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .build(); @BeforeEach void init() { - MockitoAnnotations.openMocks(this); enclaveCreds = createCredentials(); schedulerCreds = createCredentials(); final String hash = HashUtils.concatenateAndHash(schedulerCreds.getAddress(), CHAIN_TASK_ID, enclaveCreds.getAddress()); @@ -78,6 +88,7 @@ void init() { .signature(signature) .build(); when(signatureService.getAddress()).thenReturn(schedulerCreds.getAddress()); + when(resultRepositoryConfiguration.createResultProxyClientFromURL(any())).thenReturn(resultProxyClient); } @Test @@ -88,7 +99,7 @@ void shouldReturnFalseWhenEmptyToken() { when(signatureService.createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, EMPTY_ADDRESS)) .thenReturn(workerpoolAuthorization); when(resultProxyClient.getJwt(anyString(), any())).thenReturn(""); - assertThat(resultService.isResultUploaded(CHAIN_TASK_ID)).isFalse(); + assertThat(resultService.isResultUploaded(taskDescription)).isFalse(); verify(signatureService).createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, EMPTY_ADDRESS); verify(resultProxyClient).getJwt(signature.getValue(), workerpoolAuthorization); } @@ -103,7 +114,7 @@ void shouldReturnFalseWhenUnauthorizedToUpload() { .thenReturn(workerpoolAuthorization); when(resultProxyClient.getJwt(anyString(), any())).thenReturn("token"); when(resultProxyClient.isResultUploaded("token", CHAIN_TASK_ID)).thenThrow(FeignException.Unauthorized.class); - assertThatThrownBy(() -> resultService.isResultUploaded(CHAIN_TASK_ID)) + assertThatThrownBy(() -> resultService.isResultUploaded(taskDescription)) .isInstanceOf(FeignException.Unauthorized.class); verify(signatureService).createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, enclaveCreds.getAddress()); } @@ -116,7 +127,7 @@ void shouldReturnTrueWhenStandardTaskResultUploaded() { when(signatureService.createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, EMPTY_ADDRESS)) .thenReturn(workerpoolAuthorization); when(resultProxyClient.getJwt(anyString(), any())).thenReturn("token"); - assertThat(resultService.isResultUploaded(CHAIN_TASK_ID)).isTrue(); + assertThat(resultService.isResultUploaded(taskDescription)).isTrue(); verify(signatureService).createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, EMPTY_ADDRESS); } @@ -129,7 +140,7 @@ void shouldReturnTrueWhenTeeTaskResultUploaded() { when(signatureService.createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, enclaveCreds.getAddress())) .thenReturn(workerpoolAuthorization); when(resultProxyClient.getJwt(anyString(), any())).thenReturn("token"); - assertThat(resultService.isResultUploaded(CHAIN_TASK_ID)).isTrue(); + assertThat(resultService.isResultUploaded(taskDescription)).isTrue(); verify(signatureService).createAuthorization(schedulerCreds.getAddress(), CHAIN_TASK_ID, enclaveCreds.getAddress()); } From def1febe74f105cf4970f7cbcd3ee87468a483e2 Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:31:09 +0100 Subject: [PATCH 10/19] Reorder static and final keywords (#717) --- CHANGELOG.md | 4 ++++ .../contribution/ConsensusHelperTests.java | 6 ++--- .../contribution/ContributionHelperTests.java | 8 +++---- .../contribution/PredictionHelperTests.java | 6 ++--- ...ndFinalizationUnnotifiedDetectorTests.java | 4 ++-- .../ContributionUnnotifiedDetectorTests.java | 4 ++-- ...icateResultUploadTimeoutDetectorTests.java | 4 ++-- .../replicate/RevealTimeoutDetectorTests.java | 4 ++-- .../RevealUnnotifiedDetectorTests.java | 4 ++-- .../replicate/ReplicateListenersTests.java | 4 ++-- .../ReplicateSupplyServiceTests.java | 24 +++++++++---------- .../core/security/ChallengeServiceTests.java | 6 ++--- .../com/iexec/core/task/TaskTestsUtils.java | 18 +++++++------- 13 files changed, 50 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d641b04..b93e7bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ All notable changes to this project will be documented in this file. This is due to **Nethermind v1.14.7+99775bf7** where filters are lost on restart. (#715) - Use Result Proxy URL defined in deal parameters if any, fall back to scheduler default one otherwise. (#716) +### Quality + +- Reorder static and final keywords. (#717) + ### Dependency Upgrades - Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#713) diff --git a/src/test/java/com/iexec/core/contribution/ConsensusHelperTests.java b/src/test/java/com/iexec/core/contribution/ConsensusHelperTests.java index 4f90f0f0..18716174 100644 --- a/src/test/java/com/iexec/core/contribution/ConsensusHelperTests.java +++ b/src/test/java/com/iexec/core/contribution/ConsensusHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2024 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. @@ -29,8 +29,8 @@ class ConsensusHelperTests { - private final static String CHAIN_TASK_ID = "0xtaskId"; - private final static long MAX_EXECUTION_TIME = 60000; + private static final String CHAIN_TASK_ID = "0xtaskId"; + private static final long MAX_EXECUTION_TIME = 60000; /* * diff --git a/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java b/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java index d052da82..d5c3d068 100644 --- a/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java +++ b/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2024 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. @@ -31,9 +31,9 @@ class ContributionHelperTests { - private final static String A = "0xA"; - private final static String B = "0xB"; - private final static long MAX_EXECUTION_TIME = 60000; + private static final String A = "0xA"; + private static final String B = "0xB"; + private static final long MAX_EXECUTION_TIME = 60000; @Test void shouldGetContributedWeight() { diff --git a/src/test/java/com/iexec/core/contribution/PredictionHelperTests.java b/src/test/java/com/iexec/core/contribution/PredictionHelperTests.java index 5bb9998e..74289341 100644 --- a/src/test/java/com/iexec/core/contribution/PredictionHelperTests.java +++ b/src/test/java/com/iexec/core/contribution/PredictionHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2024 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. @@ -29,8 +29,8 @@ class PredictionHelperTests { - private final static String CHAIN_TASK_ID = "0xtaskId"; - private final static long MAX_EXECUTION_TIME = 60000; + private static final String CHAIN_TASK_ID = "0xtaskId"; + private static final long MAX_EXECUTION_TIME = 60000; @Test void shouldGetEmptyContributedBestPrediction() { diff --git a/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java index cc6e2fbb..8ce8416a 100644 --- a/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java @@ -48,8 +48,8 @@ import static org.mockito.Mockito.when; class ContributionAndFinalizationUnnotifiedDetectorTests { - private final static String CHAIN_TASK_ID = "chainTaskId"; - private final static String WALLET_ADDRESS = "0x1"; + private static final String CHAIN_TASK_ID = "chainTaskId"; + private static final String WALLET_ADDRESS = "0x1"; @Mock private TaskService taskService; diff --git a/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java index 3f6569a1..28db48de 100644 --- a/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java @@ -50,8 +50,8 @@ class ContributionUnnotifiedDetectorTests { - private final static String CHAIN_TASK_ID = "chainTaskId"; - private final static String WALLET_ADDRESS = "0x1"; + private static final String CHAIN_TASK_ID = "chainTaskId"; + private static final String WALLET_ADDRESS = "0x1"; @Mock private TaskService taskService; diff --git a/src/test/java/com/iexec/core/detector/replicate/ReplicateResultUploadTimeoutDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/ReplicateResultUploadTimeoutDetectorTests.java index 80cff5ea..c0775898 100644 --- a/src/test/java/com/iexec/core/detector/replicate/ReplicateResultUploadTimeoutDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/ReplicateResultUploadTimeoutDetectorTests.java @@ -47,8 +47,8 @@ class ReplicateResultUploadTimeoutDetectorTests { - private final static String WALLET_WORKER_1 = "0x748e091bf16048cb5103E0E10F9D5a8b7fBDd860"; - private final static String WALLET_WORKER_2 = "0x748e091bf16048cb5103E0E10F9D5a8b7fBDd861"; + private static final String WALLET_WORKER_1 = "0x748e091bf16048cb5103E0E10F9D5a8b7fBDd860"; + private static final String WALLET_WORKER_2 = "0x748e091bf16048cb5103E0E10F9D5a8b7fBDd861"; @Mock private TaskService taskService; diff --git a/src/test/java/com/iexec/core/detector/replicate/RevealTimeoutDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/RevealTimeoutDetectorTests.java index 890017ed..b9ae6bb4 100644 --- a/src/test/java/com/iexec/core/detector/replicate/RevealTimeoutDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/RevealTimeoutDetectorTests.java @@ -47,8 +47,8 @@ class RevealTimeoutDetectorTests { - private final static String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; - private final static String WALLET_WORKER_2 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd249"; + private static final String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; + private static final String WALLET_WORKER_2 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd249"; @Mock private TaskService taskService; diff --git a/src/test/java/com/iexec/core/detector/replicate/RevealUnnotifiedDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/RevealUnnotifiedDetectorTests.java index 2e6d49a9..f9017045 100644 --- a/src/test/java/com/iexec/core/detector/replicate/RevealUnnotifiedDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/RevealUnnotifiedDetectorTests.java @@ -48,8 +48,8 @@ class RevealUnnotifiedDetectorTests { - private final static String CHAIN_TASK_ID = "chainTaskId"; - private final static String WALLET_ADDRESS = "0x1"; + private static final String CHAIN_TASK_ID = "chainTaskId"; + private static final String WALLET_ADDRESS = "0x1"; @Mock private TaskService taskService; diff --git a/src/test/java/com/iexec/core/replicate/ReplicateListenersTests.java b/src/test/java/com/iexec/core/replicate/ReplicateListenersTests.java index 2c5252f5..9ac1f012 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateListenersTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateListenersTests.java @@ -40,8 +40,8 @@ class ReplicateListenersTests { - private final static String CHAIN_TASK_ID = "chainTaskId"; - private final static String WORKER_WALLET = "0xwallet1"; + private static final String CHAIN_TASK_ID = "chainTaskId"; + private static final String WORKER_WALLET = "0xwallet1"; @Mock private WorkerService workerService; diff --git a/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java b/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java index a6b6532b..d8ae200e 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java @@ -60,18 +60,18 @@ @ExtendWith(MockitoExtension.class) class ReplicateSupplyServiceTests { - private final static String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; - private final static String WALLET_WORKER_2 = "0xdcfeffee1443fbf9277e6fa3b50cf3b38f7101af"; - - private final static String CHAIN_TASK_ID = "0x65bc5e94ed1486b940bd6cc0013c418efad58a0a52a3d08cee89faaa21970426"; - private final static String CHAIN_TASK_ID_2 = "0xc536af16737e02bb28100452a932056d499be3c462619751a9ed36515de64d50"; - - private final static String DAPP_NAME = "dappName"; - private final static String COMMAND_LINE = "commandLine"; - private final static String NO_TEE_TAG = BytesUtils.EMPTY_HEX_STRING_32; - private final static String TEE_TAG = TeeUtils.TEE_SCONE_ONLY_TAG; //any supported TEE tag - private final static String ENCLAVE_CHALLENGE = "dummyEnclave"; - private final static long maxExecutionTime = 60000; + private static final String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; + private static final String WALLET_WORKER_2 = "0xdcfeffee1443fbf9277e6fa3b50cf3b38f7101af"; + + private static final String CHAIN_TASK_ID = "0x65bc5e94ed1486b940bd6cc0013c418efad58a0a52a3d08cee89faaa21970426"; + private static final String CHAIN_TASK_ID_2 = "0xc536af16737e02bb28100452a932056d499be3c462619751a9ed36515de64d50"; + + private static final String DAPP_NAME = "dappName"; + private static final String COMMAND_LINE = "commandLine"; + private static final String NO_TEE_TAG = BytesUtils.EMPTY_HEX_STRING_32; + private static final String TEE_TAG = TeeUtils.TEE_SCONE_ONLY_TAG; //any supported TEE tag + private static final String ENCLAVE_CHALLENGE = "dummyEnclave"; + private static final long maxExecutionTime = 60000; long workerLastBlock = 12; @Mock diff --git a/src/test/java/com/iexec/core/security/ChallengeServiceTests.java b/src/test/java/com/iexec/core/security/ChallengeServiceTests.java index e8c4ac30..f352c08c 100644 --- a/src/test/java/com/iexec/core/security/ChallengeServiceTests.java +++ b/src/test/java/com/iexec/core/security/ChallengeServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2024 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. @@ -22,8 +22,8 @@ class ChallengeServiceTests { - private final static String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; - private final static String WALLET_WORKER_2 = "0x2a69b2eb604db8eba185df03ea4f5288dcbbd248"; + private static final String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; + private static final String WALLET_WORKER_2 = "0x2a69b2eb604db8eba185df03ea4f5288dcbbd248"; private final ChallengeService challengeService = new ChallengeService(); diff --git a/src/test/java/com/iexec/core/task/TaskTestsUtils.java b/src/test/java/com/iexec/core/task/TaskTestsUtils.java index 0d9a1046..852cf5a4 100644 --- a/src/test/java/com/iexec/core/task/TaskTestsUtils.java +++ b/src/test/java/com/iexec/core/task/TaskTestsUtils.java @@ -24,17 +24,17 @@ import java.util.Date; public class TaskTestsUtils { - public final static String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; - public final static String WALLET_WORKER_2 = "0x2a69b2eb604db8eba185df03ea4f5288dcbbd248"; + public static final String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; + public static final String WALLET_WORKER_2 = "0x2a69b2eb604db8eba185df03ea4f5288dcbbd248"; - public final static String CHAIN_DEAL_ID = "0xd82223e5feff6720792ffed1665e980da95e5d32b177332013eaba8edc07f31c"; - public final static String CHAIN_TASK_ID = "0x65bc5e94ed1486b940bd6cc0013c418efad58a0a52a3d08cee89faaa21970426"; + public static final String CHAIN_DEAL_ID = "0xd82223e5feff6720792ffed1665e980da95e5d32b177332013eaba8edc07f31c"; + public static final String CHAIN_TASK_ID = "0x65bc5e94ed1486b940bd6cc0013c418efad58a0a52a3d08cee89faaa21970426"; - public final static String DAPP_NAME = "dappName"; - public final static String COMMAND_LINE = "commandLine"; - public final static String NO_TEE_TAG = BytesUtils.EMPTY_HEX_STRING_32; - public final static String TEE_TAG = TeeUtils.TEE_SCONE_ONLY_TAG; //any supported TEE tag - public final static String RESULT_LINK = "/ipfs/the_result_string"; + public static final String DAPP_NAME = "dappName"; + public static final String COMMAND_LINE = "commandLine"; + public static final String NO_TEE_TAG = BytesUtils.EMPTY_HEX_STRING_32; + public static final String TEE_TAG = TeeUtils.TEE_SCONE_ONLY_TAG; //any supported TEE tag + public static final String RESULT_LINK = "/ipfs/the_result_string"; public static Task getStubTask() { final Task task = new Task(CHAIN_DEAL_ID, 0, DAPP_NAME, COMMAND_LINE, 1, 60000, NO_TEE_TAG); From bb4675a35538d0f4f30beb99082aaf1f933a2480 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Wed, 20 Nov 2024 14:12:37 +0100 Subject: [PATCH 11/19] Push scheduler Result Proxy URL as Web2 secret to SMS (#718) --- CHANGELOG.md | 4 + gradle.properties | 2 +- .../java/com/iexec/core/sms/SmsService.java | 85 ++++++++- .../core/task/event/TaskInitializedEvent.java | 24 +++ .../core/task/update/TaskUpdateManager.java | 2 + .../com/iexec/core/sms/SmsServiceTests.java | 161 +++++++++++------- .../task/update/TaskUpdateManagerTests.java | 21 ++- 7 files changed, 222 insertions(+), 77 deletions(-) create mode 100644 src/main/java/com/iexec/core/task/event/TaskInitializedEvent.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b93e7bbd..09bee367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/vNEXT) 2024 +### New Features + +- Push scheduler Result Proxy URL as Web2 secret to SMS. (#718) + ### Bug Fixes - Start scheduler in out-of-service mode. (#712) diff --git a/gradle.properties b/gradle.properties index bc459341..25405dfc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,6 @@ iexecCommonVersion=8.5.0 iexecCommonsPocoVersion=4.1.0 iexecBlockchainAdapterVersion=8.5.0 iexecResultVersion=8.5.0 -iexecSmsVersion=8.6.0 +iexecSmsVersion=8.6.0-NEXT-SNAPSHOT nexusUser nexusPassword diff --git a/src/main/java/com/iexec/core/sms/SmsService.java b/src/main/java/com/iexec/core/sms/SmsService.java index 511f4525..89545c96 100644 --- a/src/main/java/com/iexec/core/sms/SmsService.java +++ b/src/main/java/com/iexec/core/sms/SmsService.java @@ -16,35 +16,59 @@ package com.iexec.core.sms; +import com.iexec.commons.poco.task.TaskDescription; import com.iexec.commons.poco.tee.TeeFramework; import com.iexec.commons.poco.tee.TeeUtils; import com.iexec.commons.poco.utils.BytesUtils; +import com.iexec.commons.poco.utils.HashUtils; +import com.iexec.core.chain.IexecHubService; import com.iexec.core.chain.SignatureService; +import com.iexec.core.configuration.ResultRepositoryConfiguration; import com.iexec.core.registry.PlatformRegistryConfiguration; +import com.iexec.core.task.event.TaskInitializedEvent; import com.iexec.sms.api.SmsClient; import com.iexec.sms.api.SmsClientProvider; import feign.FeignException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; +import org.web3j.crypto.Hash; import java.util.Optional; +import static com.iexec.sms.secret.ReservedSecretKeyName.IEXEC_RESULT_IEXEC_RESULT_PROXY_URL; + @Slf4j @Service public class SmsService { + private final IexecHubService iexecHubService; private final PlatformRegistryConfiguration registryConfiguration; + private final ResultRepositoryConfiguration resultRepositoryConfiguration; private final SignatureService signatureService; private final SmsClientProvider smsClientProvider; - public SmsService(PlatformRegistryConfiguration registryConfiguration, - SignatureService signatureService, - SmsClientProvider smsClientProvider) { + public SmsService(final IexecHubService iexecHubService, + final PlatformRegistryConfiguration registryConfiguration, + final ResultRepositoryConfiguration resultRepositoryConfiguration, + final SignatureService signatureService, + final SmsClientProvider smsClientProvider) { + this.iexecHubService = iexecHubService; this.registryConfiguration = registryConfiguration; + this.resultRepositoryConfiguration = resultRepositoryConfiguration; this.signatureService = signatureService; this.smsClientProvider = smsClientProvider; } + private Optional getVerifiedSmsUrl(final String chainTaskId) { + final TaskDescription taskDescription = iexecHubService.getTaskDescription(chainTaskId); + return getVerifiedSmsUrl(chainTaskId, taskDescription.getTeeFramework()); + } + + public Optional getVerifiedSmsUrl(final String chainTaskId, final String tag) { + return getVerifiedSmsUrl(chainTaskId, TeeUtils.getTeeFramework(tag)); + } + /** * Checks the following conditions: *
    @@ -55,12 +79,12 @@ public SmsService(PlatformRegistryConfiguration registryConfiguration, *

    * If any of these conditions is wrong, then the {@link SmsClient} is considered to be not-ready. * - * @param chainTaskId ID of the on-chain task. - * @param tag Tag of the deal. + * @param chainTaskId ID of the on-chain task. + * @param teeFrameworkForDeal Tag of the deal. * @return SMS url if TEE types of tag & SMS match. */ - public Optional getVerifiedSmsUrl(String chainTaskId, String tag) { - final TeeFramework teeFrameworkForDeal = TeeUtils.getTeeFramework(tag); + Optional getVerifiedSmsUrl(final String chainTaskId, final TeeFramework teeFrameworkForDeal) { + log.debug("getVerifiedSmsUrl [chainTaskId:{}, teeFrameworkForDeal:{}]", chainTaskId, teeFrameworkForDeal); if (teeFrameworkForDeal == null) { log.error("Can't get verified SMS url with invalid TEE framework " + "from tag [chainTaskId:{}]", chainTaskId); @@ -147,4 +171,51 @@ Optional generateEnclaveChallenge(String chainTaskId, String smsUrl) { } return Optional.empty(); } + + /** + * Checks if default Result Proxy URL is present in SMS. + * + * @param smsURL The SMS URL to check. + * @return {@literal true} if the URL is present, {@literal false} otherwise. + * @throws FeignException runtime exception if {@code isWeb2SecretSet} HTTP return code differs from 2XX or 404. + */ + private boolean isWorkerpoolResultProxyUrlPresent(final String smsURL) { + try { + final SmsClient smsClient = smsClientProvider.getSmsClient(smsURL); + smsClient.isWeb2SecretSet(signatureService.getAddress(), IEXEC_RESULT_IEXEC_RESULT_PROXY_URL); + return true; + } catch (FeignException.NotFound e) { + log.info("Worker pool default Result Proxy URL does not exist in SMS"); + } + return false; + } + + /** + * Pushes worker pool default Result Proxy URL to SMS. + * + * @param event The default result-proxy URL. + */ + @EventListener + public void pushWorkerpoolResultProxyUrl(final TaskInitializedEvent event) { + log.debug("pushWorkerpoolResultProxyUrl [event:{}]", event); + try { + final String resultProxyURL = resultRepositoryConfiguration.getResultRepositoryURL(); + final String smsURL = getVerifiedSmsUrl(event.getChainTaskId()).orElseThrow(); + log.debug("Pushing result-proxy default URL to SMS [sms:{}, result-proxy:{}]", smsURL, resultProxyURL); + final SmsClient smsClient = smsClientProvider.getSmsClient(smsURL); + final String challenge = HashUtils.concatenateAndHash( + Hash.sha3String("IEXEC_SMS_DOMAIN"), + signatureService.getAddress(), + Hash.sha3String(IEXEC_RESULT_IEXEC_RESULT_PROXY_URL), + Hash.sha3String(resultProxyURL)); + final String authorization = signatureService.sign(challenge).getValue(); + if (isWorkerpoolResultProxyUrlPresent(smsURL)) { + smsClient.updateWeb2Secret(authorization, signatureService.getAddress(), IEXEC_RESULT_IEXEC_RESULT_PROXY_URL, resultProxyURL); + } else { + smsClient.setWeb2Secret(authorization, signatureService.getAddress(), IEXEC_RESULT_IEXEC_RESULT_PROXY_URL, resultProxyURL); + } + } catch (Exception e) { + log.error("Failed to push default result-proxy URL to SMS", e); + } + } } diff --git a/src/main/java/com/iexec/core/task/event/TaskInitializedEvent.java b/src/main/java/com/iexec/core/task/event/TaskInitializedEvent.java new file mode 100644 index 00000000..43ad73c0 --- /dev/null +++ b/src/main/java/com/iexec/core/task/event/TaskInitializedEvent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2024 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. + * 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 com.iexec.core.task.event; + +import lombok.Value; + +@Value +public class TaskInitializedEvent { + String chainTaskId; +} diff --git a/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java b/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java index 1b3c4854..2890fe8a 100644 --- a/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java +++ b/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java @@ -286,6 +286,8 @@ void initializing2Initialized(Task task) { task.setEnclaveChallenge(enclaveChallenge.get()); update.set("enclaveChallenge", enclaveChallenge.get()); taskService.updateTask(task.getChainTaskId(), task.getCurrentStatus(), update); + // Send TaskInitializedEvent to trigger Result Proxy URL publishing to SMS + applicationEventPublisher.publishEvent(new TaskInitializedEvent(task.getChainTaskId())); replicatesService.createEmptyReplicateList(task.getChainTaskId()); updateTaskStatusAndSave(task, INITIALIZED, null); diff --git a/src/test/java/com/iexec/core/sms/SmsServiceTests.java b/src/test/java/com/iexec/core/sms/SmsServiceTests.java index 2b590e46..be92d0c5 100644 --- a/src/test/java/com/iexec/core/sms/SmsServiceTests.java +++ b/src/test/java/com/iexec/core/sms/SmsServiceTests.java @@ -18,86 +18,83 @@ import com.iexec.commons.poco.chain.WorkerpoolAuthorization; import com.iexec.commons.poco.security.Signature; +import com.iexec.commons.poco.task.TaskDescription; import com.iexec.commons.poco.tee.TeeFramework; import com.iexec.commons.poco.tee.TeeUtils; import com.iexec.commons.poco.utils.BytesUtils; +import com.iexec.core.chain.IexecHubService; import com.iexec.core.chain.SignatureService; +import com.iexec.core.configuration.ResultRepositoryConfiguration; import com.iexec.core.registry.PlatformRegistryConfiguration; +import com.iexec.core.task.event.TaskInitializedEvent; import com.iexec.sms.api.SmsClient; import com.iexec.sms.api.SmsClientProvider; import feign.FeignException; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; -import java.util.List; import java.util.Optional; -import java.util.stream.Stream; -import static org.mockito.ArgumentMatchers.anyString; +import static com.iexec.sms.secret.ReservedSecretKeyName.IEXEC_RESULT_IEXEC_RESULT_PROXY_URL; import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) +@ExtendWith(OutputCaptureExtension.class) class SmsServiceTests { + private static final String ADDRESS = "address"; private static final String AUTHORIZATION = "authorization"; + private static final String RESULT_PROXY_URL = "http://result-proxy"; private static final String GRAMINE_SMS_URL = "http://gramine-sms"; private static final String SCONE_SMS_URL = "http://scone-sms"; private static final String CHAIN_TASK_ID = "chainTaskId"; - private static final String url = "url"; + private static final String URL = "url"; + @Mock + private IexecHubService iexecHubService; + @Mock + private PlatformRegistryConfiguration registryConfiguration; + @Mock + private ResultRepositoryConfiguration resultRepositoryConfiguration; @Mock private SignatureService signatureService; @Mock private SmsClient smsClient; @Mock private SmsClientProvider smsClientProvider; - @Mock - private PlatformRegistryConfiguration registryConfiguration; @InjectMocks private SmsService smsService; - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - when(registryConfiguration.getSconeSms()).thenReturn(SCONE_SMS_URL); + @Test + void shouldGetVerifiedGramineSmsUrl() { when(registryConfiguration.getGramineSms()).thenReturn(GRAMINE_SMS_URL); - when(signatureService.createAuthorization("", CHAIN_TASK_ID, "")) - .thenReturn(WorkerpoolAuthorization.builder().signature(new Signature(AUTHORIZATION)).build()); + when(smsClientProvider.getSmsClient(GRAMINE_SMS_URL)).thenReturn(smsClient); + when(smsClient.getTeeFramework()).thenReturn(TeeUtils.getTeeFramework(TeeUtils.TEE_GRAMINE_ONLY_TAG)); + + Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, TeeUtils.TEE_GRAMINE_ONLY_TAG)) + .isEqualTo(Optional.of(GRAMINE_SMS_URL)); + + verify(smsClientProvider).getSmsClient(GRAMINE_SMS_URL); + verify(smsClient).getTeeFramework(); } - // region isSmsClientReady - static Stream validData() { - List supportedTeeTags = - List.of( - TeeUtils.TEE_SCONE_ONLY_TAG, - TeeUtils.TEE_GRAMINE_ONLY_TAG); - //Ensure all TeeEnclaveProvider are handled - // (adding a new one would break assertion) - Assertions.assertThat(supportedTeeTags) - .hasSize(TeeFramework.values().length); - return Stream.of( - Arguments.of(supportedTeeTags.get(0), SCONE_SMS_URL), - Arguments.of(supportedTeeTags.get(1), GRAMINE_SMS_URL) - ); - } - - @ParameterizedTest - @MethodSource("validData") - void shouldGetVerifiedSmsUrl(String inputTag, String expectedSmsUrl) { - when(smsClientProvider.getSmsClient(expectedSmsUrl)).thenReturn(smsClient); - when(smsClient.getTeeFramework()).thenReturn(TeeUtils.getTeeFramework(inputTag)); - - Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, inputTag)) - .isEqualTo(Optional.of(expectedSmsUrl)); - - verify(smsClientProvider).getSmsClient(expectedSmsUrl); + @Test + void shouldGetVerifiedSconeSmsUrl() { + when(registryConfiguration.getSconeSms()).thenReturn(SCONE_SMS_URL); + when(smsClientProvider.getSmsClient(SCONE_SMS_URL)).thenReturn(smsClient); + when(smsClient.getTeeFramework()).thenReturn(TeeUtils.getTeeFramework(TeeUtils.TEE_SCONE_ONLY_TAG)); + + Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, TeeUtils.TEE_SCONE_ONLY_TAG)) + .isEqualTo(Optional.of(SCONE_SMS_URL)); + + verify(smsClientProvider).getSmsClient(SCONE_SMS_URL); verify(smsClient).getTeeFramework(); } @@ -106,12 +103,12 @@ void shouldNotGetVerifiedSmsUrlSinceCannotGetEnclaveProviderFromTag() { Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, "0xabc")) .isEmpty(); - verify(smsClientProvider, times(0)).getSmsClient(anyString()); - verify(smsClient, times(0)).getTeeFramework(); + verifyNoInteractions(smsClientProvider, smsClient); } @Test void shouldNotGetVerifiedSmsUrlSinceWrongTeeEnclaveProviderOnRemoteSms() { + when(registryConfiguration.getGramineSms()).thenReturn(GRAMINE_SMS_URL); when(smsClientProvider.getSmsClient(GRAMINE_SMS_URL)).thenReturn(smsClient); when(smsClient.getTeeFramework()).thenReturn(TeeFramework.SCONE); @@ -134,11 +131,11 @@ void shouldGetEmptyAddressForStandardTask() { @Test void shouldGetEnclaveChallengeForTeeTask() { - String expected = "challenge"; - when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + final String expected = "challenge"; + initEnclaveChallengeStubs(); when(smsClient.generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID)).thenReturn(expected); - Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, url); + Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, URL); verify(smsClient).generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID); Assertions.assertThat(received) .isEqualTo(Optional.of(expected)); @@ -146,9 +143,9 @@ void shouldGetEnclaveChallengeForTeeTask() { @Test void shouldNotGetEnclaveChallengeForTeeTaskWhenEmptySmsResponse() { - when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + initEnclaveChallengeStubs(); when(smsClient.generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID)).thenReturn(""); - Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, url); + Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, URL); verify(smsClient).generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID); Assertions.assertThat(received).isEmpty(); } @@ -159,42 +156,90 @@ void shouldNotGetEnclaveChallengeForTeeTaskWhenEmptySmsResponse() { void shouldGenerateEnclaveChallenge() { final String expected = "challenge"; - when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + initEnclaveChallengeStubs(); when(smsClient.generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID)).thenReturn(expected); - Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, url); + Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, URL); Assertions.assertThat(received) .contains(expected); } @Test void shouldNotGenerateEnclaveChallengeSinceNoPublicKeyReturned() { - when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + initEnclaveChallengeStubs(); when(smsClient.generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID)).thenReturn(""); - Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, url); + Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, URL); Assertions.assertThat(received) .isEmpty(); } @Test void shouldNotGenerateEnclaveChallengeSinceFeignException() { - when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + initEnclaveChallengeStubs(); when(smsClient.generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID)).thenThrow(FeignException.GatewayTimeout.class); - Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, url); + Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, URL); Assertions.assertThat(received) .isEmpty(); } @Test void shouldNotGenerateEnclaveChallengeSinceRuntimeException() { - when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + initEnclaveChallengeStubs(); when(smsClient.generateTeeChallenge(AUTHORIZATION, CHAIN_TASK_ID)).thenThrow(RuntimeException.class); - Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, url); + Optional received = smsService.generateEnclaveChallenge(CHAIN_TASK_ID, URL); Assertions.assertThat(received) .isEmpty(); } // endregion + + // region pushWorkerpoolResultProxyUrl + @Test + void shouldDoNothingWhenSmsCommunicationFails(CapturedOutput output) { + initResultProxyStubs(); + when(smsClient.getTeeFramework()).thenThrow(FeignException.class); + smsService.pushWorkerpoolResultProxyUrl(new TaskInitializedEvent(CHAIN_TASK_ID)); + Assertions.assertThat(output.getOut()).contains("Failed to push default result-proxy URL to SMS"); + } + + @Test + void shouldAddWorkerPoolResultProxyUrl() { + initResultProxyStubs(); + when(smsClient.getTeeFramework()).thenReturn(TeeFramework.SCONE); + when(signatureService.getAddress()).thenReturn(ADDRESS); + when(signatureService.sign(anyString())).thenReturn(new Signature(AUTHORIZATION)); + doThrow(FeignException.NotFound.class).when(smsClient).isWeb2SecretSet(ADDRESS, IEXEC_RESULT_IEXEC_RESULT_PROXY_URL); + smsService.pushWorkerpoolResultProxyUrl(new TaskInitializedEvent(CHAIN_TASK_ID)); + verify(smsClient).setWeb2Secret(AUTHORIZATION, ADDRESS, IEXEC_RESULT_IEXEC_RESULT_PROXY_URL, RESULT_PROXY_URL); + } + + @Test + void shouldUpdateWorkerPoolResultProxyUrl() { + initResultProxyStubs(); + when(smsClient.getTeeFramework()).thenReturn(TeeFramework.SCONE); + when(signatureService.getAddress()).thenReturn(ADDRESS); + when(signatureService.sign(anyString())).thenReturn(new Signature(AUTHORIZATION)); + doNothing().when(smsClient).isWeb2SecretSet(ADDRESS, IEXEC_RESULT_IEXEC_RESULT_PROXY_URL); + smsService.pushWorkerpoolResultProxyUrl(new TaskInitializedEvent(CHAIN_TASK_ID)); + verify(smsClient).updateWeb2Secret(AUTHORIZATION, ADDRESS, IEXEC_RESULT_IEXEC_RESULT_PROXY_URL, RESULT_PROXY_URL); + } + // endregion + + // region utils + private void initEnclaveChallengeStubs() { + when(smsClientProvider.getSmsClient(URL)).thenReturn(smsClient); + when(signatureService.createAuthorization("", CHAIN_TASK_ID, "")) + .thenReturn(WorkerpoolAuthorization.builder().signature(new Signature(AUTHORIZATION)).build()); + } + + private void initResultProxyStubs() { + when(resultRepositoryConfiguration.getResultRepositoryURL()).thenReturn(RESULT_PROXY_URL); + when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)).thenReturn( + TaskDescription.builder().teeFramework(TeeFramework.SCONE).build()); + when(registryConfiguration.getSconeSms()).thenReturn(SCONE_SMS_URL); + when(smsClientProvider.getSmsClient(SCONE_SMS_URL)).thenReturn(smsClient); + } + // endregion } diff --git a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java index f57cbcf2..af6c7782 100644 --- a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java +++ b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java @@ -34,6 +34,7 @@ import com.iexec.core.task.*; import com.iexec.core.task.event.PleaseUploadEvent; import com.iexec.core.task.event.TaskFailedEvent; +import com.iexec.core.task.event.TaskInitializedEvent; import com.iexec.core.task.event.TaskStatusesCountUpdatedEvent; import com.iexec.core.worker.Worker; import com.iexec.core.worker.WorkerService; @@ -87,7 +88,7 @@ @ExtendWith(MockitoExtension.class) @ExtendWith(OutputCaptureExtension.class) class TaskUpdateManagerTests { - private static final String smsUrl = "smsUrl"; + private static final String SMS_URL = "smsUrl"; private static final String ERROR_MSG = "Cannot update task [chainTaskId:%s, currentStatus:%s, expectedStatus:%s, method:%s]"; @Container @@ -348,8 +349,8 @@ void shouldUpdateInitializing2InitializeFailedSinceEnclaveChallengeIsEmpty() { when(iexecHubService.getChainTask(CHAIN_TASK_ID)).thenReturn(Optional.of(ChainTask.builder() .contributionDeadline(Instant.now().plus(60L, ChronoUnit.MINUTES).toEpochMilli()) .build())); - when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, TEE_TAG)).thenReturn(Optional.of(smsUrl)); - when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, smsUrl)).thenReturn(Optional.empty()); + when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, TEE_TAG)).thenReturn(Optional.of(SMS_URL)); + when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, SMS_URL)).thenReturn(Optional.empty()); taskUpdateManager.updateTask(task.getChainTaskId()); @@ -360,8 +361,7 @@ void shouldUpdateInitializing2InitializeFailedSinceEnclaveChallengeIsEmpty() { @Test void shouldUpdateReceived2Initializing2InitializedOnStandard() { final Task task = getStubTask(); - String tag = NO_TEE_TAG; - task.setTag(tag); + task.setTag(NO_TEE_TAG); taskRepository.save(task); when(iexecHubService.hasEnoughGas()).thenReturn(true); @@ -382,7 +382,7 @@ void shouldUpdateReceived2Initializing2InitializedOnStandard() { assertThatTaskContainsStatuses(resultTask, INITIALIZED, List.of(RECEIVED, INITIALIZING, INITIALIZED)); assertThat(resultTask.getEnclaveChallenge()).isEqualTo(BytesUtils.EMPTY_ADDRESS); assertThat(resultTask.getSmsUrl()).isNull(); - verify(smsService, times(0)).getVerifiedSmsUrl(anyString(), anyString()); + verify(applicationEventPublisher).publishEvent(new TaskInitializedEvent(CHAIN_TASK_ID)); } @Test @@ -402,17 +402,16 @@ void shouldUpdateReceived2Initializing2InitializedOnTee() { when(iexecHubService.getChainTask(CHAIN_TASK_ID)).thenReturn(Optional.of(ChainTask.builder() .contributionDeadline(Instant.now().plus(60L, ChronoUnit.MINUTES).toEpochMilli()) .build())); - when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, tag)) - .thenReturn(Optional.of(smsUrl)); - when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, smsUrl)).thenReturn(Optional.of(BytesUtils.EMPTY_ADDRESS)); + when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, tag)).thenReturn(Optional.of(SMS_URL)); + when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, SMS_URL)).thenReturn(Optional.of(BytesUtils.EMPTY_ADDRESS)); taskUpdateManager.updateTask(CHAIN_TASK_ID); final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); assertThat(resultTask.getChainDealId()).isEqualTo(CHAIN_DEAL_ID); assertThatTaskContainsStatuses(resultTask, INITIALIZED, List.of(RECEIVED, INITIALIZING, INITIALIZED)); assertThat(resultTask.getEnclaveChallenge()).isEqualTo(BytesUtils.EMPTY_ADDRESS); - assertThat(resultTask.getSmsUrl()).isEqualTo(smsUrl); - verify(smsService, times(1)).getVerifiedSmsUrl(CHAIN_TASK_ID, tag); + assertThat(resultTask.getSmsUrl()).isEqualTo(SMS_URL); + verify(applicationEventPublisher).publishEvent(new TaskInitializedEvent(CHAIN_TASK_ID)); } @Test From 5fc12ce2e67218df30798e8d268a5860d3028afe Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:49:48 +0100 Subject: [PATCH 12/19] Update contribution detector tests to reflect new TEE task flow changes (#719) --- CHANGELOG.md | 2 + gradle.properties | 2 +- ...ndFinalizationUnnotifiedDetectorTests.java | 114 ++++++++---------- .../ContributionUnnotifiedDetectorTests.java | 52 +++----- 4 files changed, 70 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09bee367..c7099cb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ All notable changes to this project will be documented in this file. ### Quality - Reorder static and final keywords. (#717) +- Update `contribution` and `contributionAndFinalize` detector tests. + TEE tasks with callback are now eligible to `contributeAndFinalize` flow. (#719) ### Dependency Upgrades diff --git a/gradle.properties b/gradle.properties index 25405dfc..ca7e4aef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ version=8.5.0 iexecCommonVersion=8.5.0 -iexecCommonsPocoVersion=4.1.0 +iexecCommonsPocoVersion=4.1.0-NEXT-SNAPSHOT iexecBlockchainAdapterVersion=8.5.0 iexecResultVersion=8.5.0 iexecSmsVersion=8.6.0-NEXT-SNAPSHOT diff --git a/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java index 8ce8416a..7e8e50c7 100644 --- a/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/ContributionAndFinalizationUnnotifiedDetectorTests.java @@ -18,11 +18,8 @@ import com.iexec.common.replicate.ReplicateStatus; import com.iexec.common.replicate.ReplicateStatusUpdate; -import com.iexec.commons.poco.chain.ChainReceipt; import com.iexec.commons.poco.task.TaskDescription; -import com.iexec.commons.poco.utils.BytesUtils; import com.iexec.core.chain.IexecHubService; -import com.iexec.core.chain.Web3jService; import com.iexec.core.configuration.CronConfiguration; import com.iexec.core.replicate.Replicate; import com.iexec.core.replicate.ReplicatesService; @@ -31,14 +28,19 @@ import com.iexec.core.task.TaskStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigInteger; import java.util.Collections; import java.util.List; +import java.util.stream.Stream; import static com.iexec.common.replicate.ReplicateStatus.*; import static com.iexec.common.replicate.ReplicateStatusModifier.WORKER; @@ -47,9 +49,11 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class ContributionAndFinalizationUnnotifiedDetectorTests { private static final String CHAIN_TASK_ID = "chainTaskId"; private static final String WALLET_ADDRESS = "0x1"; + private static final String CALLBACK = "callback"; @Mock private TaskService taskService; @@ -63,9 +67,6 @@ class ContributionAndFinalizationUnnotifiedDetectorTests { @Mock private CronConfiguration cronConfiguration; - @Mock - private Web3jService web3jService; - @Spy @InjectMocks private ContributionAndFinalizationUnnotifiedDetector detector; @@ -75,16 +76,10 @@ class ContributionAndFinalizationUnnotifiedDetectorTests { @BeforeEach void init() { - MockitoAnnotations.openMocks(this); ReflectionTestUtils.setField(detector, "detectorRate", 1000); - when(iexecHubService.getTaskDescription(anyString())).thenReturn(TaskDescription.builder() - .trust(BigInteger.ONE) - .isTeeTask(true) - .callback(BytesUtils.EMPTY_ADDRESS) - .build()); } - private Replicate getReplicateWithStatus(ReplicateStatus replicateStatus) { + private Replicate getReplicateWithStatus(final ReplicateStatus replicateStatus) { Replicate replicate = new Replicate(WALLET_ADDRESS, CHAIN_TASK_ID); ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() .modifier(WORKER).status(replicateStatus).build(); @@ -92,6 +87,19 @@ private Replicate getReplicateWithStatus(ReplicateStatus replicateStatus) { return replicate; } + // Helper method to avoid redundancy + private void mockTaskAndTaskDecription(final String callback) { + Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); + when(iexecHubService.getTaskDescription(anyString())).thenReturn( + TaskDescription.builder() + .trust(BigInteger.ONE) + .isTeeTask(true) + .callback(callback) + .build() + ); + when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); + } + // region detectOnChainChanges /** @@ -102,21 +110,14 @@ private Replicate getReplicateWithStatus(ReplicateStatus replicateStatus) { *

  • 1 time from {@link ContributionAndFinalizationUnnotifiedDetector#detectOnchainDone()}
  • * */ - @Test - void shouldDetectBothChangesOnChain() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); + @ParameterizedTest + @ValueSource(strings = {"", CALLBACK}) + void shouldDetectBothChangesOnChain(final String callback) { + mockTaskAndTaskDecription(callback); Replicate replicate = getReplicateWithStatus(CONTRIBUTE_AND_FINALIZE_ONGOING); when(replicatesService.getReplicates(CHAIN_TASK_ID)).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isRevealed(CHAIN_TASK_ID, WALLET_ADDRESS)).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); - when(iexecHubService.getFinalizeBlock(CHAIN_TASK_ID, 0)) - .thenReturn(ChainReceipt.builder() - .blockNumber(10L) - .txHash("0xabcef") - .build() - ); for (int i = 0; i < 10; i++) { detector.detectOnChainChanges(); @@ -134,21 +135,14 @@ void shouldDetectBothChangesOnChain() { // region detectOnchainDoneWhenOffchainOngoing (ContributeAndFinalizeOngoing) - @Test - void shouldDetectMissedUpdateSinceOffChainOngoing() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); + @ParameterizedTest + @ValueSource(strings = {"", CALLBACK}) + void shouldDetectMissedUpdateSinceOffChainOngoing(final String callback) { + mockTaskAndTaskDecription(callback); Replicate replicate = getReplicateWithStatus(CONTRIBUTE_AND_FINALIZE_ONGOING); when(replicatesService.getReplicates(CHAIN_TASK_ID)).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isRevealed(CHAIN_TASK_ID, WALLET_ADDRESS)).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); - when(iexecHubService.getFinalizeBlock(CHAIN_TASK_ID, 0)) - .thenReturn(ChainReceipt.builder() - .blockNumber(10L) - .txHash("0xabcef") - .build() - ); detector.detectOnchainDoneWhenOffchainOngoing(); @@ -160,25 +154,19 @@ void shouldDetectMissedUpdateSinceOffChainOngoing() { ); } - @Test - void shouldNotDetectMissedUpdateSinceNotOffChainOngoing() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); - - Replicate replicate = getReplicateWithStatus(COMPUTED); - when(replicatesService.getReplicates(CHAIN_TASK_ID)).thenReturn(Collections.singletonList(replicate)); - when(iexecHubService.isRevealed(CHAIN_TASK_ID, WALLET_ADDRESS)).thenReturn(true); - + @ParameterizedTest + @ValueSource(strings = {"", CALLBACK}) + void shouldNotDetectMissedUpdateSinceNotOffChainOngoing(final String callback) { detector.detectOnchainDoneWhenOffchainOngoing(); Mockito.verify(replicatesService, never()) .updateReplicateStatus(any(), any(), any(ReplicateStatusUpdate.class)); } - @Test - void shouldNotDetectMissedUpdateSinceNotOnChainDone() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); + @ParameterizedTest + @ValueSource(strings = {"", CALLBACK}) + void shouldNotDetectMissedUpdateSinceNotOnChainDone(final String callback) { + mockTaskAndTaskDecription(callback); Replicate replicate = getReplicateWithStatus(CONTRIBUTE_AND_FINALIZE_ONGOING); when(replicatesService.getReplicates(CHAIN_TASK_ID)).thenReturn(Collections.singletonList(replicate)); @@ -193,20 +181,23 @@ void shouldNotDetectMissedUpdateSinceNotOnChainDone() { // region detectOnchainDone (REVEALED) + static Stream provideReplicateStatusAndCallback() { + return Stream.of( + Arguments.of(ReplicateStatus.COMPUTED, ""), + Arguments.of(ReplicateStatus.COMPUTED, CALLBACK), + Arguments.of(ReplicateStatus.CONTRIBUTE_AND_FINALIZE_ONGOING, ""), + Arguments.of(ReplicateStatus.CONTRIBUTE_AND_FINALIZE_ONGOING, CALLBACK) + ); + } + @ParameterizedTest - @EnumSource(value = ReplicateStatus.class, names = {"COMPUTED", "CONTRIBUTE_AND_FINALIZE_ONGOING"}) - void shouldDetectMissedUpdateSinceOnChainDoneNotOffChainDone(ReplicateStatus replicateStatus) { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); + @MethodSource("provideReplicateStatusAndCallback") + void shouldDetectMissedUpdateSinceOnChainDoneNotOffChainDone(final ReplicateStatus replicateStatus, final String callback) { + mockTaskAndTaskDecription(callback); Replicate replicate = getReplicateWithStatus(replicateStatus); when(replicatesService.getReplicates(CHAIN_TASK_ID)).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isRevealed(CHAIN_TASK_ID, WALLET_ADDRESS)).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); - when(iexecHubService.getFinalizeBlock(CHAIN_TASK_ID, 0)).thenReturn(ChainReceipt.builder() - .blockNumber(10L) - .txHash("0xabcef") - .build()); detector.detectOnchainDone(); @@ -227,7 +218,6 @@ void shouldNotDetectMissedUpdateSinceOnChainDoneAndOffChainDone() { Replicate replicate = getReplicateWithStatus(CONTRIBUTE_AND_FINALIZE_DONE); when(replicatesService.getReplicates(CHAIN_TASK_ID)).thenReturn(Collections.singletonList(replicate)); - when(iexecHubService.isRevealed(CHAIN_TASK_ID, WALLET_ADDRESS)).thenReturn(true); detector.detectOnchainDone(); Mockito.verify(replicatesService, never()) @@ -243,12 +233,6 @@ void shouldNotDetectMissedUpdateSinceOnChainDoneAndNotEligibleToContributeAndFin Replicate replicate = getReplicateWithStatus(CONTRIBUTING); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); - when(iexecHubService.isContributed(any(), any())).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); - when(iexecHubService.getContributionBlock(anyString(), anyString(), anyLong())).thenReturn(ChainReceipt.builder() - .blockNumber(10L) - .txHash("0xabcef") - .build()); detector.detectOnchainDone(); diff --git a/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java b/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java index 28db48de..5c0ec483 100644 --- a/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/replicate/ContributionUnnotifiedDetectorTests.java @@ -22,7 +22,6 @@ import com.iexec.commons.poco.task.TaskDescription; import com.iexec.commons.poco.utils.BytesUtils; import com.iexec.core.chain.IexecHubService; -import com.iexec.core.chain.Web3jService; import com.iexec.core.configuration.CronConfiguration; import com.iexec.core.replicate.Replicate; import com.iexec.core.replicate.ReplicatesService; @@ -31,9 +30,11 @@ import com.iexec.core.task.TaskStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigInteger; @@ -48,6 +49,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class ContributionUnnotifiedDetectorTests { private static final String CHAIN_TASK_ID = "chainTaskId"; @@ -65,25 +67,16 @@ class ContributionUnnotifiedDetectorTests { @Mock private CronConfiguration cronConfiguration; - @Mock - private Web3jService web3jService; - @Spy @InjectMocks private ContributionUnnotifiedDetector contributionDetector; @BeforeEach void init() { - MockitoAnnotations.openMocks(this); ReflectionTestUtils.setField(contributionDetector, "detectorRate", 1000); - when(iexecHubService.getTaskDescription(anyString())).thenReturn(TaskDescription.builder() - .trust(BigInteger.ONE) - .isTeeTask(true) - .callback("0x1") - .build()); } - private Replicate getReplicateWithStatus(ReplicateStatus replicateStatus) { + private Replicate getReplicateWithStatus(final ReplicateStatus replicateStatus) { Replicate replicate = new Replicate(WALLET_ADDRESS, CHAIN_TASK_ID); ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() .modifier(WORKER).status(replicateStatus).build(); @@ -91,6 +84,16 @@ private Replicate getReplicateWithStatus(ReplicateStatus replicateStatus) { return replicate; } + // Helper method to avoid redundancy + private void mockTaskAndTaskDescription() { + Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); + when(iexecHubService.getTaskDescription(anyString())).thenReturn(TaskDescription.builder() + .trust(BigInteger.ONE) + .isTeeTask(false) + .build()); + when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); + } + // region detectOnChainChanges /** @@ -103,13 +106,10 @@ private Replicate getReplicateWithStatus(ReplicateStatus replicateStatus) { */ @Test void shouldDetectBothChangesOnChain() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); - + mockTaskAndTaskDescription(); Replicate replicate = getReplicateWithStatus(CONTRIBUTING); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isContributed(any(), any())).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); when(iexecHubService.getContributionBlock(anyString(), anyString(), anyLong())).thenReturn(ChainReceipt.builder() .blockNumber(10L) .txHash("0xabcef") @@ -129,13 +129,10 @@ void shouldDetectBothChangesOnChain() { @Test void shouldDetectMissedUpdateSinceOffChainOngoing() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); - + mockTaskAndTaskDescription(); Replicate replicate = getReplicateWithStatus(CONTRIBUTING); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isContributed(any(), any())).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); when(iexecHubService.getContributionBlock(anyString(), anyString(), anyLong())) .thenReturn(ChainReceipt.builder().blockNumber(10L).txHash("0xabcef").build()); @@ -152,7 +149,6 @@ void shouldNotDetectMissedUpdateSinceNotOffChainOngoing() { Replicate replicate = getReplicateWithStatus(COMPUTED); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); - when(iexecHubService.isContributed(any(), any())).thenReturn(true); contributionDetector.detectOnchainDoneWhenOffchainOngoing(); @@ -162,9 +158,7 @@ void shouldNotDetectMissedUpdateSinceNotOffChainOngoing() { @Test void shouldNotDetectMissedUpdateSinceNotOnChainDone() { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); - + mockTaskAndTaskDescription(); Replicate replicate = getReplicateWithStatus(CONTRIBUTING); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isContributed(any(), any())).thenReturn(false); @@ -181,13 +175,10 @@ void shouldNotDetectMissedUpdateSinceNotOnChainDone() { @ParameterizedTest @EnumSource(value = ReplicateStatus.class, names = {"COMPUTED", "CONTRIBUTING"}) void shouldDetectMissedUpdateSinceOnChainDoneNotOffChainDone(ReplicateStatus replicateStatus) { - Task task = Task.builder().chainTaskId(CHAIN_TASK_ID).build(); - when(taskService.findByCurrentStatus(TaskStatus.getWaitingContributionStatuses())).thenReturn(Collections.singletonList(task)); - + mockTaskAndTaskDescription(); Replicate replicate = getReplicateWithStatus(replicateStatus); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); when(iexecHubService.isContributed(any(), any())).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); when(iexecHubService.getContributionBlock(anyString(), anyString(), anyLong())).thenReturn(ChainReceipt.builder() .blockNumber(10L) .txHash("0xabcef") @@ -212,7 +203,6 @@ void shouldNotDetectMissedUpdateSinceOnChainDoneAndOffChainDone() { Replicate replicate = getReplicateWithStatus(CONTRIBUTED); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); - when(iexecHubService.isContributed(any(), any())).thenReturn(true); contributionDetector.detectOnchainDone(); Mockito.verify(replicatesService, never()) @@ -228,12 +218,6 @@ void shouldNotDetectMissedUpdateSinceOnChainDoneAndEligibleToContributeAndFinali Replicate replicate = getReplicateWithStatus(CONTRIBUTING); when(replicatesService.getReplicates(any())).thenReturn(Collections.singletonList(replicate)); - when(iexecHubService.isContributed(any(), any())).thenReturn(true); - when(web3jService.getLatestBlockNumber()).thenReturn(11L); - when(iexecHubService.getContributionBlock(anyString(), anyString(), anyLong())).thenReturn(ChainReceipt.builder() - .blockNumber(10L) - .txHash("0xabcef") - .build()); contributionDetector.detectOnchainDone(); From 87c36b83979e4f7fbe854bfbae9d1722d2489b07 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Thu, 28 Nov 2024 10:58:22 +0100 Subject: [PATCH 13/19] Update off-chain task model with on-chain task consensus data in all workflows (#720) --- CHANGELOG.md | 1 + .../core/task/update/TaskUpdateManager.java | 112 ++++++++++++++---- .../task/update/TaskUpdateManagerTests.java | 71 ++++++----- 3 files changed, 134 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7099cb0..787d1d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. - Scheduler needs to enter out-of-service mode on first blockchain communication loss. This is due to **Nethermind v1.14.7+99775bf7** where filters are lost on restart. (#715) - Use Result Proxy URL defined in deal parameters if any, fall back to scheduler default one otherwise. (#716) +- Update off-chain task model with on-chain task consensus data in all workflows. (#720) ### Quality diff --git a/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java b/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java index 2890fe8a..7553adb7 100644 --- a/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java +++ b/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java @@ -49,8 +49,11 @@ import static com.iexec.core.task.TaskStatus.*; -@Service +/** + * Class to update off-chain {@link Task task} model on notification from {@link TaskUpdateRequestManager}. + */ @Slf4j +@Service class TaskUpdateManager { private final TaskService taskService; @@ -106,7 +109,7 @@ void updateTask(String chainTaskId) { initialized2Running(task, chainTask); break; case RUNNING: - transitionFromRunningState(task, chainTask); + transitionFromRunningState(chainTask, task); break; case CONSENSUS_REACHED: consensusReached2AtLeastOneReveal2ResultUploading(task); @@ -155,6 +158,7 @@ void updateTask(String chainTaskId) { } // region database + /** * Creates one or several task status changes for the task before committing all of them to the database. * @@ -208,6 +212,7 @@ void saveTask(String chainTaskId, TaskStatus currentStatus, TaskStatus wishedSta chainTaskId, currentStatus, wishedStatus); } } + // endregion // region status transitions @@ -348,7 +353,18 @@ void initialized2Running(Task task, ChainTask chainTask) { } } - void transitionFromRunningState(Task task, ChainTask chainTask) { + // region transitionFromRunningState + + /** + * Handle transition when an off-chain task in the {@code RUNNING} state. + *

    + * This method handles contribution timeouts, whether replicates list is empty and task transition to the next state + * depending on the task eligibility to {@code contributeAndFinalize} flow. + * + * @param chainTask On-chain task + * @param task Off-chain task model of the on-chain task + */ + void transitionFromRunningState(final ChainTask chainTask, final Task task) { log.debug("transitionFromRunningState [chainTaskId:{}]", task.getChainTaskId()); if (task.getCurrentStatus() != RUNNING) { emitError(task, RUNNING, "transitionFromRunningState"); @@ -373,13 +389,13 @@ void transitionFromRunningState(Task task, ChainTask chainTask) { final TaskDescription taskDescription = iexecHubService.getTaskDescription(task.getChainTaskId()); + log.debug("Task eligibility to contributeAndFinalize flow [chainTaskId:{}, contributeAndFinalize:{}]", + chainTaskId, taskDescription.isEligibleToContributeAndFinalize()); if (taskDescription.isEligibleToContributeAndFinalize()) { // running2Finalized2Completed must be the first call to prevent other transition execution - log.debug("Task is running in a TEE without callback, flow contributeAndFinalize is possible [chainTaskId:{}]", - chainTaskId); - running2Finalized2Completed(task, replicatesList); + running2Finalized2Completed(chainTask, task, replicatesList); } else { - // Task is either TEE with callback or non-TEE task + // Task is a non-TEE task running2ConsensusReached(chainTask, task, replicatesList); } @@ -389,32 +405,79 @@ void transitionFromRunningState(Task task, ChainTask chainTask) { } } - private void running2ConsensusReached(ChainTask chainTask, Task task, ReplicatesList replicatesList) { + /** + * Update off-chain task model with on-chain task consensus related data. + * + * @param chainTask On-chain task + * @param task Off-chain task model of the on-chain task + * @return The updated off-chain task model containing consensus data from the on-chain task + */ + private Task updateConsensusDataFromChainTask(final ChainTask chainTask, final Task task) { + task.setRevealDeadline(new Date(chainTask.getRevealDeadline())); + task.setConsensus(chainTask.getConsensusValue()); + final long consensusBlockNumber = iexecHubService.getConsensusBlock(task.getChainTaskId(), task.getInitializationBlockNumber()).getBlockNumber(); + task.setConsensusReachedBlockNumber(consensusBlockNumber); + taskService.updateTask(task.getChainTaskId(), task.getCurrentStatus(), + Update.update("revealDeadline", task.getRevealDeadline()) + .set("consensus", task.getConsensus()) + .set("consensusReachedBlockNumber", task.getConsensusReachedBlockNumber())); + return task; + } + + /** + * Check if an on-chain task not eligible to {@code contributeAndFinalize} exits its {@code RUNNING} state. + *

    + * When such a task exits the {@code RUNNING} state, the {@code contributeAndFinalize} method from the PoCo has been + * executed. The {@code contribute} and {@code checkConsensus} methods have been executed and on-chain data + * should have the following properties: + *

      + *
    • The on-chain task status is {@code REVEALING} + *
    • The on-chain task {@code consensusValue} and {@revealDeadline} fields have been updated + *
    • The {@code TaskConsensus} event has been emitted and can be found in the blockchain logs + *
    + * + * @param chainTask On-chain task + * @param task Off-chain task model of the on-chain task + * @param replicatesList List of off-chain replicates + * @see + * IexecPoco2Delegate Smart Contract + */ + private void running2ConsensusReached(final ChainTask chainTask, final Task task, final ReplicatesList replicatesList) { log.debug("running2ConsensusReached [chainTaskId:{}]", task.getChainTaskId()); final String chainTaskId = task.getChainTaskId(); final boolean isConsensusReached = taskService.isConsensusReached(replicatesList); if (isConsensusReached) { - // change the revealDeadline and consensus of the task from the chainTask info - task.setRevealDeadline(new Date(chainTask.getRevealDeadline())); - task.setConsensus(chainTask.getConsensusValue()); - long consensusBlockNumber = iexecHubService.getConsensusBlock(chainTaskId, task.getInitializationBlockNumber()).getBlockNumber(); - task.setConsensusReachedBlockNumber(consensusBlockNumber); - taskService.updateTask(task.getChainTaskId(), task.getCurrentStatus(), - Update.update("revealDeadline", task.getRevealDeadline()) - .set("consensus", task.getConsensus()) - .set("consensusReachedBlockNumber", task.getConsensusReachedBlockNumber())); - updateTaskStatusAndSave(task, CONSENSUS_REACHED); + final Task taskWithConsensus = updateConsensusDataFromChainTask(chainTask, task); + updateTaskStatusAndSave(taskWithConsensus, CONSENSUS_REACHED); applicationEventPublisher.publishEvent(ConsensusReachedEvent.builder() .chainTaskId(chainTaskId) - .consensus(task.getConsensus()) - .blockNumber(task.getConsensusReachedBlockNumber()) + .consensus(taskWithConsensus.getConsensus()) + .blockNumber(taskWithConsensus.getConsensusReachedBlockNumber()) .build()); } } - private void running2Finalized2Completed(Task task, ReplicatesList replicatesList) { + /** + * Check if an on-chain task eligible to {@code contributeAndFinalize} exits its {@code RUNNING} state. + *

    + * When such a task exits the {@code RUNNING} state, the {@code contributeAndFinalize} method from the PoCo has been + * executed. The {@code contributeAndFinalize} method has been executed and on-chain data + * should have the following properties: + *

      + *
    • The on-chain task status is {@code COMPLETED} + *
    • The on-chain task {@code consensusValue} and {@revealDeadline} fields have been updated + *
    • The {@code TaskConsensus} event has been emitted and can be found in the blockchain logs + *
    + * + * @param chainTask On-chain task + * @param task Off-chain task model of the on-chain task + * @param replicatesList List of off-chain replicates + * @see + * IexecPoco2Delegate Smart Contract + */ + private void running2Finalized2Completed(final ChainTask chainTask, final Task task, final ReplicatesList replicatesList) { log.debug("running2Finalized2Completed [chainTaskId:{}]", task.getChainTaskId()); final String chainTaskId = task.getChainTaskId(); final int nbReplicatesWithContributeAndFinalizeStatus = replicatesList.getNbReplicatesWithCurrentStatus(ReplicateStatus.CONTRIBUTE_AND_FINALIZE_DONE); @@ -430,7 +493,8 @@ private void running2Finalized2Completed(Task task, ReplicatesList replicatesLis return; } - toFinalizedToCompleted(task); + final Task taskWithConsensus = updateConsensusDataFromChainTask(chainTask, task); + toFinalizedToCompleted(taskWithConsensus); } /** @@ -442,7 +506,7 @@ private void running2Finalized2Completed(Task task, ReplicatesList replicatesLis * * @param task Task to check and to make become {@link TaskStatus#RUNNING_FAILED}. */ - private void running2RunningFailed(Task task, ReplicatesList replicatesList) { + private void running2RunningFailed(final Task task, final ReplicatesList replicatesList) { log.debug("running2RunningFailed [chainTaskId:{}]", task.getChainTaskId()); if (!task.isTeeTask()) { log.debug("This flow only applies to TEE tasks [chainTaskId:{}]", task.getChainTaskId()); @@ -483,6 +547,8 @@ private void running2RunningFailed(Task task, ReplicatesList replicatesList) { applicationEventPublisher.publishEvent(new TaskRunningFailedEvent(task.getChainTaskId())); } + // endregion + void consensusReached2AtLeastOneReveal2ResultUploading(Task task) { log.debug("consensusReached2AtLeastOneReveal2ResultUploading [chainTaskId:{}]", task.getChainTaskId()); if (task.getCurrentStatus() != CONSENSUS_REACHED) { diff --git a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java index af6c7782..be2757db 100644 --- a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java +++ b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java @@ -625,7 +625,7 @@ void shouldNotTransitionFromRunningStateWhenTaskNotRunning() { task.setTag(TeeUtils.TEE_SCONE_ONLY_TAG); taskRepository.save(task); - taskUpdateManager.transitionFromRunningState(task, ChainTask.builder().build()); + taskUpdateManager.transitionFromRunningState(ChainTask.builder().build(), task); final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); assertThat(resultTask.getCurrentStatus()).isEqualTo(INITIALIZED); } @@ -659,8 +659,8 @@ void shouldNotTransitionFromRunningStateWhenNoReplicates() { when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.empty()); - taskUpdateManager.transitionFromRunningState(task, - ChainTask.builder().chainTaskId(CHAIN_TASK_ID).status(ChainTaskStatus.ACTIVE).build()); + taskUpdateManager.transitionFromRunningState( + ChainTask.builder().chainTaskId(CHAIN_TASK_ID).status(ChainTaskStatus.ACTIVE).build(), task); final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); assertThat(resultTask.getCurrentStatus()).isEqualTo(RUNNING); } @@ -678,8 +678,8 @@ void shouldNotUpdateRunning2Finalized2CompletedWhenNoReplicatesOnContributeAndFi when(workerService.getAliveWorkers()).thenReturn(workersList); mockTaskDescriptionFromTask(task); - taskUpdateManager.transitionFromRunningState(task, - ChainTask.builder().chainTaskId(CHAIN_TASK_ID).status(ChainTaskStatus.ACTIVE).build()); + taskUpdateManager.transitionFromRunningState( + ChainTask.builder().chainTaskId(CHAIN_TASK_ID).status(ChainTaskStatus.ACTIVE).build(), task); final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); assertThat(resultTask.getCurrentStatus()).isEqualTo(RUNNING); } @@ -696,14 +696,15 @@ void shouldNotUpdateRunning2Finalized2CompletedWhenMoreThanOneReplicatesOnContri when(replicatesList.getNbReplicatesWithCurrentStatus(ReplicateStatus.CONTRIBUTE_AND_FINALIZE_DONE)).thenReturn(2); mockTaskDescriptionFromTask(task); - taskUpdateManager.transitionFromRunningState(task, - ChainTask.builder().chainTaskId(CHAIN_TASK_ID).status(ChainTaskStatus.ACTIVE).build()); + taskUpdateManager.transitionFromRunningState( + ChainTask.builder().chainTaskId(CHAIN_TASK_ID).status(ChainTaskStatus.ACTIVE).build(), task); final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); assertThat(resultTask.getCurrentStatus()).isEqualTo(FAILED); } @Test - void shouldUpdateRunning2ConsensusReached() { + void shouldUpdateRunning2ConsensusReached(final CapturedOutput output) { + final Instant start = Instant.now(); final Task task = getStubTask(RUNNING); taskRepository.save(task); @@ -712,6 +713,7 @@ void shouldUpdateRunning2ConsensusReached() { when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); when(iexecHubService.getChainTask(task.getChainTaskId())).thenReturn(Optional.of(ChainTask.builder() .status(ChainTaskStatus.REVEALING) + .revealDeadline(Instant.now().toEpochMilli()) .winnerCounter(2) .build())); when(iexecHubService.getConsensusBlock(anyString(), anyLong())).thenReturn(ChainReceipt.builder().blockNumber(1L).build()); @@ -721,6 +723,40 @@ void shouldUpdateRunning2ConsensusReached() { taskUpdateManager.updateTask(task.getChainTaskId()); final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); assertThat(resultTask.getCurrentStatus()).isEqualTo(CONSENSUS_REACHED); + assertThat(resultTask.getConsensusReachedBlockNumber()).isOne(); + assertThat(resultTask.getRevealDeadline()).isBetween(Date.from(start), task.getFinalDeadline()); + assertThat(output.getOut()).contains( + String.format("Task eligibility to contributeAndFinalize flow [chainTaskId:%s, contributeAndFinalize:%s]", + CHAIN_TASK_ID, false)); + } + + @Test + void shouldUpdateTaskRunning2Finalized2Completed(final CapturedOutput output) { + final Instant start = Instant.now(); + final Task task = getStubTask(RUNNING); + task.setTag(TeeUtils.TEE_SCONE_ONLY_TAG); + taskRepository.save(task); + + final ReplicatesList replicatesList = spy(new ReplicatesList(CHAIN_TASK_ID)); + + mockChainTask(); + when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); + when(replicatesList.getNbReplicatesWithCurrentStatus(ReplicateStatus.CONTRIBUTE_AND_FINALIZE_DONE)).thenReturn(1); + when(iexecHubService.getChainTask(task.getChainTaskId())).thenReturn(Optional.of(ChainTask.builder() + .status(ChainTaskStatus.COMPLETED) + .revealDeadline(Instant.now().toEpochMilli()) + .build())); + when(iexecHubService.getConsensusBlock(anyString(), anyLong())).thenReturn(ChainReceipt.builder().blockNumber(1L).build()); + mockTaskDescriptionFromTask(task); + + taskUpdateManager.updateTask(CHAIN_TASK_ID); + final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); + assertThatTaskContainsStatuses(resultTask, COMPLETED, List.of(RECEIVED, RUNNING, FINALIZED, COMPLETED)); + assertThat(resultTask.getConsensusReachedBlockNumber()).isOne(); + assertThat(resultTask.getRevealDeadline()).isBetween(Date.from(start), task.getFinalDeadline()); + assertThat(output.getOut()).contains( + String.format("Task eligibility to contributeAndFinalize flow [chainTaskId:%s, contributeAndFinalize:%s]", + CHAIN_TASK_ID, true)); } @Test @@ -1627,25 +1663,6 @@ void shouldNotUpdateFromResultUploadedToFinalizingSinceNotEnoughGas() { assertThat(resultTask.getCurrentStatus()).isEqualTo(RESULT_UPLOADED); } - @Test - void shouldUpdateTaskRunning2Finalized2Completed() { - final Task task = getStubTask(RUNNING); - task.setTag(TeeUtils.TEE_SCONE_ONLY_TAG); - taskRepository.save(task); - - final ReplicatesList replicatesList = spy(new ReplicatesList(CHAIN_TASK_ID)); - - mockChainTask(); - when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); - when(replicatesList.getNbReplicatesWithCurrentStatus(ReplicateStatus.CONTRIBUTE_AND_FINALIZE_DONE)).thenReturn(1); - - mockTaskDescriptionFromTask(task); - - taskUpdateManager.updateTask(CHAIN_TASK_ID); - final Task resultTask = taskRepository.findByChainTaskId(task.getChainTaskId()).orElseThrow(); - assertThatTaskContainsStatuses(resultTask, COMPLETED, List.of(RECEIVED, RUNNING, FINALIZED, COMPLETED)); - } - @Test void shouldUpdateFromAnyInProgressStatus2FinalDeadlineReached() { final Task task = getStubTask(); From 9291fb072de60bcd7d7d784545edd59ef3909380 Mon Sep 17 00:00:00 2001 From: Nathan GAUD <80476483+Natchica@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:56:10 +0100 Subject: [PATCH 14/19] Upgrade to testcontainers 1.20.4 (#721) --- CHANGELOG.md | 1 + build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 787d1d03..b51f55e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file. - Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#713) - Upgrade to Gradle 8.10.2. (#714) +- Upgrade to `testcontainers` 1.20.4. (#721) ## [[8.5.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.5.0) 2024-06-19 diff --git a/build.gradle b/build.gradle index 7a6de86b..8ba5c70c 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ ext { springCloudVersion = '2021.0.8' jjwtVersion = '0.11.5' mongockVersion = '4.2.7.BETA' - testContainersVersion = '1.19.0' + testContainersVersion = '1.20.4' } if (!project.hasProperty('gitBranch')) { From f4691536a374f08f44845313469b06177049082f Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:58:30 +0100 Subject: [PATCH 15/19] Upgrade to mongo 7.0.15-jammy (#722) --- CHANGELOG.md | 1 + build.gradle | 2 +- .../ContributionTimeoutTaskDetectorTests.java | 13 ++++++------ .../task/FinalDeadlineTaskDetectorTests.java | 13 ++++++------ .../core/replicate/ReplicateServiceTests.java | 21 ++++--------------- .../iexec/core/worker/WorkerServiceTests.java | 8 ++++--- 6 files changed, 25 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b51f55e4..7ef75f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ All notable changes to this project will be documented in this file. - Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#713) - Upgrade to Gradle 8.10.2. (#714) - Upgrade to `testcontainers` 1.20.4. (#721) +- Upgrade to `mongo:7.0.15-jammy`. (#722) ## [[8.5.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.5.0) 2024-06-19 diff --git a/build.gradle b/build.gradle index 8ba5c70c..e510712e 100644 --- a/build.gradle +++ b/build.gradle @@ -133,7 +133,7 @@ testing { tasks.withType(Test).configureEach { finalizedBy jacocoTestReport - systemProperty "mongo.image", "mongo:4.4.28-focal" + systemProperty "mongo.image", "mongo:7.0.15-jammy" } tasks.register('itest') { diff --git a/src/test/java/com/iexec/core/detector/task/ContributionTimeoutTaskDetectorTests.java b/src/test/java/com/iexec/core/detector/task/ContributionTimeoutTaskDetectorTests.java index 1047a7a9..d0929a25 100644 --- a/src/test/java/com/iexec/core/detector/task/ContributionTimeoutTaskDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/task/ContributionTimeoutTaskDetectorTests.java @@ -23,8 +23,9 @@ import com.iexec.core.task.TaskStatusChange; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.context.ApplicationEventPublisher; @@ -47,6 +48,7 @@ @DataMongoTest @Testcontainers +@ExtendWith(MockitoExtension.class) class ContributionTimeoutTaskDetectorTests { @Container @@ -72,7 +74,6 @@ static void registerProperties(DynamicPropertyRegistry registry) { @BeforeEach void init() { - MockitoAnnotations.openMocks(this); taskRepository.deleteAll(); final TaskService taskService = new TaskService(mongoTemplate, taskRepository, iexecHubService, applicationEventPublisher); contributionTimeoutTaskDetector = new ContributionTimeoutTaskDetector(taskService, applicationEventPublisher); @@ -86,7 +87,7 @@ void shouldNotDetectTaskAfterContributionDeadlineIfNotInitializedOrRunning() { contributionTimeoutTaskDetector.detect(); - Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); + final Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); assertThat(finalTask).isNotNull(); assertThat(finalTask.getCurrentStatus()).isEqualTo(AT_LEAST_ONE_REVEALED); assertThat(finalTask.getDateStatusList()).isNotNull(); @@ -102,7 +103,7 @@ void shouldNotDetectTaskIfBeforeTimeout() { contributionTimeoutTaskDetector.detect(); - Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); + final Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); assertThat(finalTask).isNotNull(); assertThat(finalTask.getCurrentStatus()).isEqualTo(RUNNING); assertThat(finalTask.getDateStatusList().stream().map(TaskStatusChange::getStatus)) @@ -118,7 +119,7 @@ void shouldDetectTaskIfBetweenContributionAndFinalDeadline() { contributionTimeoutTaskDetector.detect(); - Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); + final Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); assertThat(finalTask).isNotNull(); assertThat(finalTask.getCurrentStatus()).isEqualTo(FAILED); assertThat(finalTask.getDateStatusList().stream().map(TaskStatusChange::getStatus)) @@ -134,7 +135,7 @@ void shouldNotDetectTaskIfAfterFinalDeadline() { contributionTimeoutTaskDetector.detect(); - Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); + final Task finalTask = taskRepository.findByChainTaskId(CHAIN_TASK_ID).orElse(null); assertThat(finalTask).isNotNull(); assertThat(finalTask.getCurrentStatus()).isEqualTo(RUNNING); assertThat(finalTask.getDateStatusList().stream().map(TaskStatusChange::getStatus)) diff --git a/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java b/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java index cc3edcff..69c1cbd3 100644 --- a/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java @@ -24,8 +24,9 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.context.ApplicationEventPublisher; @@ -47,6 +48,7 @@ @Slf4j @DataMongoTest @Testcontainers +@ExtendWith(MockitoExtension.class) class FinalDeadlineTaskDetectorTests { @Container @@ -72,7 +74,6 @@ static void registerProperties(DynamicPropertyRegistry registry) { @BeforeEach void init() { - MockitoAnnotations.openMocks(this); taskRepository.deleteAll(); final TaskService taskService = new TaskService(mongoTemplate, taskRepository, iexecHubService, applicationEventPublisher); finalDeadlineTaskDetector = new FinalDeadlineTaskDetector(taskService, applicationEventPublisher); @@ -88,11 +89,11 @@ private Task getTask() { @Test void shouldDetectTaskAfterFinalDeadline() { - Task task = getTask(); + final Task task = getTask(); task.setFinalDeadline(Date.from(Instant.now().minus(1, ChronoUnit.MINUTES))); taskRepository.save(task); finalDeadlineTaskDetector.detect(); - Task finalTask = taskRepository.findByChainTaskId("0x1").orElse(null); + final Task finalTask = taskRepository.findByChainTaskId("0x1").orElse(null); assertThat(finalTask).isNotNull(); assertThat(finalTask.getDateStatusList()).isNotNull(); assertThat(finalTask.getDateStatusList().stream().map(TaskStatusChange::getStatus)) @@ -101,11 +102,11 @@ void shouldDetectTaskAfterFinalDeadline() { @Test void shouldNotDetectTaskBeforeFinalDeadline() { - Task task = getTask(); + final Task task = getTask(); task.setFinalDeadline(Date.from(Instant.now().plus(1, ChronoUnit.MINUTES))); taskRepository.save(task); finalDeadlineTaskDetector.detect(); - Task finalTask = taskRepository.findByChainTaskId("0x1").orElse(null); + final Task finalTask = taskRepository.findByChainTaskId("0x1").orElse(null); assertThat(finalTask).isNotNull(); assertThat(finalTask.getDateStatusList()).isNotNull(); assertThat(finalTask.getDateStatusList().stream().map(TaskStatusChange::getStatus)) diff --git a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java index 47544961..e515d50d 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java @@ -30,12 +30,13 @@ import io.vavr.control.Either; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.context.ApplicationEventPublisher; @@ -62,6 +63,7 @@ @DataMongoTest @Testcontainers +@ExtendWith(MockitoExtension.class) class ReplicateServiceTests { private static final UpdateReplicateStatusArgs UPDATE_ARGS = UpdateReplicateStatusArgs.builder() @@ -72,7 +74,7 @@ class ReplicateServiceTests { private static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse(System.getProperty("mongo.image"))); @DynamicPropertySource - static void registerProperties(DynamicPropertyRegistry registry) { + static void registerProperties(final DynamicPropertyRegistry registry) { registry.add("spring.data.mongodb.host", mongoDBContainer::getHost); registry.add("spring.data.mongodb.port", () -> mongoDBContainer.getMappedPort(27017)); } @@ -96,7 +98,6 @@ static void registerProperties(DynamicPropertyRegistry registry) { @BeforeEach void init() { - MockitoAnnotations.openMocks(this); TaskLogsService taskLogsService = new TaskLogsService(taskLogsRepository); replicatesService = new ReplicatesService(mongoTemplate, replicatesRepository, iexecHubService, applicationEventPublisher, web3jService, resultService, taskLogsService); @@ -675,9 +676,6 @@ void shouldNotSetContributionHashSinceRevealing() { when(web3jService.isBlockAvailable(anyLong())).thenReturn(true); when(iexecHubService.repeatIsRevealedTrue(anyString(), anyString())).thenReturn(true); - when(iexecHubService.getChainContribution(CHAIN_TASK_ID, WALLET_WORKER_1)).thenReturn(Optional.of(ChainContribution.builder() - .resultHash("hash") - .build())); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ReplicateUpdatedEvent.class); ReplicateStatusDetails details = new ReplicateStatusDetails(10L); @@ -1072,9 +1070,6 @@ void shouldAuthorizeUpdateOnResultUploaded() { .taskDescription(TaskDescription.builder().callback("callback").build()) .build(); - when(web3jService.isBlockAvailable(anyLong())).thenReturn(true); - when(iexecHubService.repeatIsContributedTrue(anyString(), anyString())).thenReturn(true); - assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, updateArgs)) .isEqualTo(ReplicateStatusUpdateError.NO_ERROR); } @@ -1169,7 +1164,6 @@ void shouldAuthorizeUpdateOnContributeAndFinalizeDone() { .thenReturn(true); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)).thenReturn(task); when(iexecHubService.isTaskInCompletedStatusOnChain(CHAIN_TASK_ID)).thenReturn(true); - when(resultService.isResultUploaded(task)).thenReturn(true); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, null)) .isEqualTo(ReplicateStatusUpdateError.NO_ERROR); @@ -1187,7 +1181,6 @@ void shouldNotAuthorizeUpdateOnContributeAndFinalizeDoneWhenNotRevealed() { when(iexecHubService.repeatIsRevealedTrue(CHAIN_TASK_ID, WALLET_WORKER_1)) .thenReturn(false); - when(iexecHubService.isTaskInCompletedStatusOnChain(CHAIN_TASK_ID)).thenReturn(true); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, null)) .isEqualTo(ReplicateStatusUpdateError.GENERIC_CANT_UPDATE); @@ -1233,7 +1226,6 @@ void shouldNotAuthorizeUpdateOnContributeAndFinalizeDoneWhenTaskNotCompleted() { when(iexecHubService.repeatIsRevealedTrue(CHAIN_TASK_ID, WALLET_WORKER_1)) .thenReturn(true); - when(iexecHubService.isTaskInCompletedStatusOnChain(CHAIN_TASK_ID)).thenReturn(false); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, null)) .isEqualTo(ReplicateStatusUpdateError.GENERIC_CANT_UPDATE); @@ -1379,8 +1371,6 @@ void computeUpdateReplicateStatusArgsContributeAndFinalizeDone() { @Test void computeUpdateReplicateStatusArgsResultUploadFailed() { - final int unexpectedWorkerWeight = 1; - final ChainContribution unexpectedChainContribution = ChainContribution.builder().build(); final String unexpectedResultLink = "resultLink"; final String unexpectedChainCallbackData = "chainCallbackData"; final TaskDescription expectedTaskDescription = TaskDescription.builder().build(); @@ -1395,9 +1385,6 @@ void computeUpdateReplicateStatusArgsResultUploadFailed() { .details(details) .build(); - when(iexecHubService.getWorkerWeight(WALLET_WORKER_1)).thenReturn(unexpectedWorkerWeight); - when(iexecHubService.getChainContribution(CHAIN_TASK_ID, WALLET_WORKER_1)) - .thenReturn(Optional.of(unexpectedChainContribution)); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)) .thenReturn(expectedTaskDescription); diff --git a/src/test/java/com/iexec/core/worker/WorkerServiceTests.java b/src/test/java/com/iexec/core/worker/WorkerServiceTests.java index 21d9c3d4..bcf023a5 100644 --- a/src/test/java/com/iexec/core/worker/WorkerServiceTests.java +++ b/src/test/java/com/iexec/core/worker/WorkerServiceTests.java @@ -25,8 +25,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.data.mongodb.core.MongoTemplate; @@ -55,6 +56,7 @@ @DataMongoTest @Testcontainers +@ExtendWith(MockitoExtension.class) class WorkerServiceTests { private static final String WORKER1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; @@ -97,7 +99,6 @@ static void initRegistry() { @BeforeEach void init() { - MockitoAnnotations.openMocks(this); workerService = new WorkerService(mongoTemplate, workerRepository, workerConfiguration); workerRepository.deleteAll(); } @@ -251,7 +252,8 @@ void shouldWorkerBeAllowedToAskReplicate() { .walletAddress(wallet) .build(); workerRepository.save(worker); - workerService.getWorkerStatsMap().computeIfAbsent(wallet, WorkerService.WorkerStats::new); + workerService.getWorkerStatsMap().computeIfAbsent(wallet, WorkerService.WorkerStats::new) + .setLastReplicateDemandDate(Date.from(Instant.now().minusSeconds(10L))); when(workerConfiguration.getAskForReplicatePeriod()).thenReturn(5000L); assertThat(workerService.isWorkerAllowedToAskReplicate(wallet)).isTrue(); From 7ece6eee392d3e71d05f40529a449ab819ab9169 Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:16:03 +0100 Subject: [PATCH 16/19] Resolve deprecations in ReplicatesService and ResultService caused by TaskDescription (#723) --- CHANGELOG.md | 1 + .../core/replicate/ReplicatesService.java | 4 +- .../com/iexec/core/result/ResultService.java | 2 +- .../core/replicate/ReplicateServiceTests.java | 58 ++++++++++++------- .../iexec/core/result/ResultServiceTests.java | 6 +- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef75f32..db2f2804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. - Reorder static and final keywords. (#717) - Update `contribution` and `contributionAndFinalize` detector tests. TEE tasks with callback are now eligible to `contributeAndFinalize` flow. (#719) +- Resolve deprecations caused by `TaskDescription` in `ReplicatesService` and `ResultService`. (#723) ### Dependency Upgrades diff --git a/src/main/java/com/iexec/core/replicate/ReplicatesService.java b/src/main/java/com/iexec/core/replicate/ReplicatesService.java index 0725e42b..71723aa5 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicatesService.java +++ b/src/main/java/com/iexec/core/replicate/ReplicatesService.java @@ -545,8 +545,8 @@ public boolean isResultUploaded(String chainTaskId) { return isResultUploaded(task); } - public boolean isResultUploaded(TaskDescription task) { - final boolean hasIpfsStorageProvider = IPFS_RESULT_STORAGE_PROVIDER.equals(task.getResultStorageProvider()); + public boolean isResultUploaded(final TaskDescription task) { + final boolean hasIpfsStorageProvider = IPFS_RESULT_STORAGE_PROVIDER.equals(task.getDealParams().getIexecResultStorageProvider()); // Offchain computing or TEE task with private storage if (task.containsCallback() || (task.isTeeTask() && !hasIpfsStorageProvider)) { diff --git a/src/main/java/com/iexec/core/result/ResultService.java b/src/main/java/com/iexec/core/result/ResultService.java index 722db2d1..cdc3c693 100644 --- a/src/main/java/com/iexec/core/result/ResultService.java +++ b/src/main/java/com/iexec/core/result/ResultService.java @@ -48,7 +48,7 @@ public ResultService(final ResultRepositoryConfiguration resultRepositoryConfigu @Retryable(value = FeignException.class) public boolean isResultUploaded(final TaskDescription taskDescription) { final String chainTaskId = taskDescription.getChainTaskId(); - final ResultProxyClient resultProxyClient = resultRepositoryConfiguration.createResultProxyClientFromURL(taskDescription.getResultStorageProxy()); + final ResultProxyClient resultProxyClient = resultRepositoryConfiguration.createResultProxyClientFromURL(taskDescription.getDealParams().getIexecResultStorageProxy()); final String enclaveChallenge = taskService.getTaskByChainTaskId(chainTaskId).map(Task::getEnclaveChallenge).orElse(EMPTY_ADDRESS); final WorkerpoolAuthorization workerpoolAuthorization = signatureService.createAuthorization(signatureService.getAddress(), chainTaskId, enclaveChallenge); final String resultProxyToken = resultProxyClient.getJwt(workerpoolAuthorization.getSignature().getValue(), workerpoolAuthorization); diff --git a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java index e515d50d..8b10862c 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java @@ -19,6 +19,7 @@ import com.iexec.common.replicate.*; import com.iexec.commons.poco.chain.ChainContribution; import com.iexec.commons.poco.chain.ChainTask; +import com.iexec.commons.poco.chain.DealParams; import com.iexec.commons.poco.task.TaskDescription; import com.iexec.commons.poco.utils.BytesUtils; import com.iexec.core.chain.IexecHubService; @@ -749,36 +750,42 @@ void shouldGetReplicateWithResultUploadedStatus() { @ParameterizedTest @ValueSource(booleans = {true, false}) - void shouldCheckResultServiceAndReturnTrue(boolean isTeeTask) { + void shouldCheckResultServiceAndReturnTrue(final boolean isTeeTask) { + final DealParams dealParams = DealParams.builder() + .iexecResultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .build(); TaskDescription taskDescription = TaskDescription.builder() .chainTaskId(CHAIN_TASK_ID) .callback(BytesUtils.EMPTY_ADDRESS) .isTeeTask(isTeeTask) - .resultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .dealParams(dealParams) .build(); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)) .thenReturn(taskDescription); when(resultService.isResultUploaded(taskDescription)).thenReturn(true); - boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); + final boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isTrue(); verify(resultService).isResultUploaded(taskDescription); } @ParameterizedTest @ValueSource(booleans = {true, false}) - void shouldCheckResultServiceAndReturnFalse(boolean isTeeTask) { - TaskDescription taskDescription = TaskDescription.builder() + void shouldCheckResultServiceAndReturnFalse(final boolean isTeeTask) { + final DealParams dealParams = DealParams.builder() + .iexecResultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .build(); + final TaskDescription taskDescription = TaskDescription.builder() .chainTaskId(CHAIN_TASK_ID) .callback(BytesUtils.EMPTY_ADDRESS) .isTeeTask(isTeeTask) - .resultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .dealParams(dealParams) .build(); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)) .thenReturn(taskDescription); when(resultService.isResultUploaded(taskDescription)).thenReturn(false); - boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); + final boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isFalse(); verify(resultService).isResultUploaded(taskDescription); } @@ -811,16 +818,19 @@ void shouldReturnTrueForCallbackTask(boolean isTeeTask) { @Test void shouldReturnTrueIfPrivateStorageForTeeTask() { - TaskDescription taskDescription = TaskDescription.builder() + final DealParams dealParams = DealParams.builder() + .iexecResultStorageProvider(DROPBOX_RESULT_STORAGE_PROVIDER) + .build(); + final TaskDescription taskDescription = TaskDescription.builder() .chainTaskId(CHAIN_TASK_ID) .callback(BytesUtils.EMPTY_ADDRESS) .isTeeTask(true) - .resultStorageProvider(DROPBOX_RESULT_STORAGE_PROVIDER) + .dealParams(dealParams) .build(); when(iexecHubService.getTaskDescription(CHAIN_TASK_ID)) .thenReturn(taskDescription); - boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); + final boolean isResultUploaded = replicatesService.isResultUploaded(CHAIN_TASK_ID); assertThat(isResultUploaded).isTrue(); verify(resultService, never()).isResultUploaded(any()); } @@ -912,10 +922,10 @@ void shouldNotAuthorizeUpdateSinceBadWorkflowTransition() { @Test void shouldNotAuthorizeUpdateSinceContributeFailed() { - Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); + final Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate.updateStatus(CONTRIBUTING, ReplicateStatusModifier.WORKER); - ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() + final ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() .modifier(WORKER) .status(CONTRIBUTE_FAILED) .build(); @@ -926,10 +936,10 @@ void shouldNotAuthorizeUpdateSinceContributeFailed() { @Test void shouldNotAuthorizeUpdateSinceRevealFailed() { - Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); + final Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate.updateStatus(REVEALING, ReplicateStatusModifier.WORKER); - ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() + final ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() .modifier(WORKER) .status(REVEAL_FAILED) .build(); @@ -940,16 +950,19 @@ void shouldNotAuthorizeUpdateSinceRevealFailed() { @Test void shouldAuthorizeUpdateOnResultUploadFailed() { - Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); + final DealParams dealParams = DealParams.builder() + .iexecResultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .build(); + final Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate.updateStatus(RESULT_UPLOADING, ReplicateStatusModifier.WORKER); - ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() + final ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() .modifier(WORKER) .status(RESULT_UPLOAD_FAILED) .build(); - UpdateReplicateStatusArgs updateReplicateStatusArgs = UpdateReplicateStatusArgs + final UpdateReplicateStatusArgs updateReplicateStatusArgs = UpdateReplicateStatusArgs .builder() - .taskDescription(TaskDescription.builder().resultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER).build()) + .taskDescription(TaskDescription.builder().dealParams(dealParams).build()) .build(); assertThat(replicatesService.canUpdateReplicateStatus(replicate, statusUpdate, updateReplicateStatusArgs)) @@ -958,10 +971,10 @@ void shouldAuthorizeUpdateOnResultUploadFailed() { @Test void shouldNotAuthorizeUpdateOnResultUploadFailedSinceResultUploadedWithCallback() { - Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); + final Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate.updateStatus(RESULT_UPLOADING, ReplicateStatusModifier.WORKER); - ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() + final ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.builder() .modifier(WORKER) .status(RESULT_UPLOAD_FAILED) .build(); @@ -1188,6 +1201,9 @@ void shouldNotAuthorizeUpdateOnContributeAndFinalizeDoneWhenNotRevealed() { @Test void shouldNotAuthorizeUpdateOnContributeAndFinalizeDoneWhenNotUploaded() { + final DealParams dealParams = DealParams.builder() + .iexecResultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .build(); final Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate.updateStatus(CONTRIBUTE_AND_FINALIZE_ONGOING, ReplicateStatusModifier.WORKER); @@ -1198,7 +1214,7 @@ void shouldNotAuthorizeUpdateOnContributeAndFinalizeDoneWhenNotUploaded() { final TaskDescription task = TaskDescription .builder() .chainTaskId(CHAIN_TASK_ID) - .resultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .dealParams(dealParams) .build(); when(iexecHubService.repeatIsRevealedTrue(CHAIN_TASK_ID, WALLET_WORKER_1)) diff --git a/src/test/java/com/iexec/core/result/ResultServiceTests.java b/src/test/java/com/iexec/core/result/ResultServiceTests.java index cf4137db..d704cc64 100644 --- a/src/test/java/com/iexec/core/result/ResultServiceTests.java +++ b/src/test/java/com/iexec/core/result/ResultServiceTests.java @@ -16,6 +16,7 @@ package com.iexec.core.result; +import com.iexec.commons.poco.chain.DealParams; import com.iexec.commons.poco.chain.WorkerpoolAuthorization; import com.iexec.commons.poco.security.Signature; import com.iexec.commons.poco.task.TaskDescription; @@ -70,9 +71,12 @@ class ResultServiceTests { private Credentials schedulerCreds; private Signature signature; private WorkerpoolAuthorization workerpoolAuthorization; + private final DealParams dealParams = DealParams.builder() + .iexecResultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .build(); private final TaskDescription taskDescription = TaskDescription.builder() .chainTaskId(CHAIN_TASK_ID) - .resultStorageProvider(IPFS_RESULT_STORAGE_PROVIDER) + .dealParams(dealParams) .build(); @BeforeEach From 0bc023df8b94e77f73fb8c1b58f8676e61088942 Mon Sep 17 00:00:00 2001 From: nabil-Tounarti <117689544+nabil-Tounarti@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:37:09 +0100 Subject: [PATCH 17/19] Add @PreDetroy annotation in services implementing Purgeable (#724) --- CHANGELOG.md | 1 + src/main/java/com/iexec/core/chain/IexecHubService.java | 6 +++++- .../com/iexec/core/replicate/ReplicateSupplyService.java | 6 +++++- .../com/iexec/core/task/update/TaskUpdateManagerTests.java | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db2f2804..22334653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file. - Update `contribution` and `contributionAndFinalize` detector tests. TEE tasks with callback are now eligible to `contributeAndFinalize` flow. (#719) - Resolve deprecations caused by `TaskDescription` in `ReplicatesService` and `ResultService`. (#723) +- Add missing `@PreDestroy` annotation in services implementing `Purgeable`. (#724) ### Dependency Upgrades diff --git a/src/main/java/com/iexec/core/chain/IexecHubService.java b/src/main/java/com/iexec/core/chain/IexecHubService.java index 5f5dc4cf..3b9d8900 100644 --- a/src/main/java/com/iexec/core/chain/IexecHubService.java +++ b/src/main/java/com/iexec/core/chain/IexecHubService.java @@ -29,6 +29,7 @@ import org.web3j.protocol.core.methods.request.EthFilter; import org.web3j.protocol.core.methods.response.TransactionReceipt; +import javax.annotation.PreDestroy; import java.math.BigInteger; import java.time.Instant; import java.util.Date; @@ -411,12 +412,15 @@ private EthFilter createEthFilter(long fromBlock, long toBlock, Event event) { // endregion @Override - public boolean purgeTask(String chainTaskId) { + public boolean purgeTask(final String chainTaskId) { + log.debug("purgeTask [chainTaskId: {}]", chainTaskId); return super.purgeTask(chainTaskId); } @Override + @PreDestroy public void purgeAllTasksData() { + log.info("Method purgeAllTasksData() called to perform task data cleanup."); super.purgeAllTasksData(); } } diff --git a/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java b/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java index 1ab27094..27c8288b 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java +++ b/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java @@ -41,6 +41,7 @@ import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; +import javax.annotation.PreDestroy; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -531,13 +532,16 @@ private TaskAbortCause getTaskAbortCause(Task task) { // region purge locks @Override - public boolean purgeTask(String chainTaskId) { + public boolean purgeTask(final String chainTaskId) { + log.debug("purgeTask [chainTaskId: {}]", chainTaskId); taskAccessForNewReplicateLocks.remove(chainTaskId); return !taskAccessForNewReplicateLocks.containsKey(chainTaskId); } @Override + @PreDestroy public void purgeAllTasksData() { + log.info("Method purgeAllTasksData() called to perform task data cleanup."); taskAccessForNewReplicateLocks.clear(); } // endregion diff --git a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java index be2757db..e8f77eb6 100644 --- a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java +++ b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTests.java @@ -164,7 +164,7 @@ void shouldNotUpgrade2ReopenedSinceCurrentStatusWrong() { @Test void shouldNotUpgrade2ReopenedSinceNotAfterRevealDeadline() { final Task task = getStubTask(CONSENSUS_REACHED); - task.setRevealDeadline(new Date(new Date().getTime() + 100)); + task.setRevealDeadline(new Date(new Date().getTime() + 500)); taskRepository.save(task); when(replicatesService.getNbReplicatesWithCurrentStatus(CHAIN_TASK_ID, ReplicateStatus.REVEALED)).thenReturn(0); From 33de81b98308ba202b1c222732a4f90fd1e5c6e9 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Mon, 23 Dec 2024 15:16:46 +0100 Subject: [PATCH 18/19] Update iExec libraries --- CHANGELOG.md | 5 +++++ gradle.properties | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22334653..62c317bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ All notable changes to this project will be documented in this file. - Upgrade to Gradle 8.10.2. (#714) - Upgrade to `testcontainers` 1.20.4. (#721) - Upgrade to `mongo:7.0.15-jammy`. (#722) +- Upgrade to `iexec-commons-poco` 4.2.0. (#725) +- Upgrade to `iexec-common` 8.6.0. (#725) +- Upgrade to `iexec-blockchain-adapter-api-library` 8.6.0. (#725) +- Upgrade to `iexec-result-proxy-library` 8.6.0. (#725) +- Upgrade to `iexec-sms-library` 8.7.0. (#725) ## [[8.5.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.5.0) 2024-06-19 diff --git a/gradle.properties b/gradle.properties index ca7e4aef..810f1d87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ version=8.5.0 -iexecCommonVersion=8.5.0 -iexecCommonsPocoVersion=4.1.0-NEXT-SNAPSHOT -iexecBlockchainAdapterVersion=8.5.0 -iexecResultVersion=8.5.0 -iexecSmsVersion=8.6.0-NEXT-SNAPSHOT +iexecCommonsPocoVersion=4.2.0 +iexecCommonVersion=8.6.0 +iexecBlockchainAdapterVersion=8.6.0 +iexecResultVersion=8.6.0 +iexecSmsVersion=8.7.0 nexusUser nexusPassword From 18d2b2d896c0211a81f7b35df008d17e31a94af1 Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Mon, 23 Dec 2024 15:42:19 +0100 Subject: [PATCH 19/19] Release 8.6.0 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c317bb..2aeb0ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/vNEXT) 2024 +## [[8.6.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.6.0) 2024-12-23 ### New Features diff --git a/gradle.properties b/gradle.properties index 810f1d87..69f5cd4a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=8.5.0 +version=8.6.0 iexecCommonsPocoVersion=4.2.0 iexecCommonVersion=8.6.0 iexecBlockchainAdapterVersion=8.6.0