├── .gitignore ├── CHANGELOG.md ├── COPYRIGHT ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.md ├── build.gradle ├── docker-compose.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lombok.config ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── iexec │ │ └── worker │ │ ├── Application.java │ │ ├── PingService.java │ │ ├── chain │ │ ├── Contribution.java │ │ ├── ContributionService.java │ │ ├── EnclaveAuthorizationService.java │ │ ├── IexecHubService.java │ │ ├── RevealService.java │ │ ├── WalletConfiguration.java │ │ ├── Web3jService.java │ │ ├── WebSocketBlockchainListener.java │ │ ├── WorkerpoolAuthorizationService.java │ │ └── event │ │ │ └── LatestBlockEvent.java │ │ ├── compute │ │ ├── ComputeController.java │ │ ├── ComputeExitCauseService.java │ │ ├── ComputeManagerService.java │ │ ├── ComputeResponse.java │ │ ├── ComputeStage.java │ │ ├── ComputeStageConverter.java │ │ ├── app │ │ │ ├── AppComputeResponse.java │ │ │ └── AppComputeService.java │ │ ├── post │ │ │ ├── PostComputeResponse.java │ │ │ └── PostComputeService.java │ │ └── pre │ │ │ ├── PreComputeResponse.java │ │ │ └── PreComputeService.java │ │ ├── config │ │ ├── ConfigServerConfigurationService.java │ │ ├── MissingConfigurationException.java │ │ ├── PublicConfigurationService.java │ │ ├── PurgeConfiguration.java │ │ ├── SchedulerConfiguration.java │ │ ├── SchedulingConfig.java │ │ ├── StompClientConfiguration.java │ │ └── WorkerConfigurationService.java │ │ ├── dataset │ │ └── DataService.java │ │ ├── docker │ │ ├── DockerRegistryConfiguration.java │ │ ├── DockerService.java │ │ └── RegistryCredentials.java │ │ ├── feign │ │ ├── BaseFeignClient.java │ │ ├── CustomCoreFeignClient.java │ │ ├── LoginService.java │ │ └── config │ │ │ └── RestTemplateConfig.java │ │ ├── metric │ │ ├── AggregatedDurations.java │ │ ├── ComputeDurationsConfig.java │ │ ├── ComputeDurationsService.java │ │ ├── MetricsController.java │ │ ├── MetricsService.java │ │ └── WorkerMetrics.java │ │ ├── pubsub │ │ ├── SessionCreatedEvent.java │ │ ├── SessionLostEvent.java │ │ ├── StompClientService.java │ │ └── SubscriptionService.java │ │ ├── replicate │ │ ├── ReplicateActionResponse.java │ │ ├── ReplicateDemandService.java │ │ └── ReplicateRecoveryService.java │ │ ├── result │ │ ├── ResultInfo.java │ │ ├── ResultService.java │ │ └── ResultUploadDetails.java │ │ ├── sgx │ │ └── SgxService.java │ │ ├── sms │ │ ├── SmsClientProviderConfiguration.java │ │ ├── SmsService.java │ │ └── TeeSessionGenerationException.java │ │ ├── task │ │ ├── TaskManagerService.java │ │ └── TaskNotificationService.java │ │ ├── tee │ │ ├── TeeService.java │ │ ├── TeeServicesManager.java │ │ ├── TeeServicesPropertiesCreationException.java │ │ ├── TeeServicesPropertiesService.java │ │ ├── gramine │ │ │ └── TeeGramineService.java │ │ └── scone │ │ │ ├── LasService.java │ │ │ ├── LasServicesManager.java │ │ │ ├── SconeConfiguration.java │ │ │ └── TeeSconeService.java │ │ ├── utils │ │ ├── AsyncUtils.java │ │ ├── ExecutorUtils.java │ │ ├── LoggingUtils.java │ │ ├── MaxSizeHashMap.java │ │ └── WorkflowException.java │ │ ├── version │ │ └── VersionController.java │ │ └── worker │ │ └── WorkerService.java └── resources │ ├── application.yml │ ├── banner.txt │ ├── entrypoint.sh │ ├── logback-spring.xml │ └── wallet │ └── encrypted-wallet_worker1.json └── test ├── java └── com │ └── iexec │ └── worker │ ├── PingServiceTests.java │ ├── TestApplication.java │ ├── TestUtils.java │ ├── chain │ ├── ContributionServiceTests.java │ ├── EnclaveAuthorizationServiceTests.java │ ├── IexecHubServiceTests.java │ ├── RevealServiceTests.java │ ├── WalletConfigurationTest.java │ ├── Web3jServiceTests.java │ ├── WebSocketBlockchainListenerTests.java │ └── WorkerpoolAuthorizationServiceTests.java │ ├── compute │ ├── ComputeControllerTests.java │ ├── ComputeExitCauseServiceTests.java │ ├── ComputeManagerServiceTests.java │ ├── ComputeStageConverterTests.java │ ├── app │ │ └── AppComputeServiceTests.java │ ├── post │ │ └── PostComputeServiceTests.java │ └── pre │ │ └── PreComputeServiceTests.java │ ├── config │ ├── ConfigServerConfigurationServiceTests.java │ ├── PublicConfigurationServiceTests.java │ ├── PurgeConfigurationTests.java │ ├── SchedulerConfigurationTests.java │ ├── StompClientConfigurationTests.java │ └── WorkerConfigurationServiceTests.java │ ├── dataset │ └── DataServiceTests.java │ ├── docker │ ├── DockerRegistryConfigurationTests.java │ └── DockerServiceTests.java │ ├── feign │ ├── CustomCoreFeignClientTests.java │ ├── LoginServiceTests.java │ └── RestTemplateConfigTests.java │ ├── metric │ ├── ComputeDurationsConfigTests.java │ ├── ComputeDurationsServiceTests.java │ ├── MetricsControllerTests.java │ └── MetricsServiceTests.java │ ├── pubsub │ ├── StompClientServiceTests.java │ └── SubscriptionServiceTests.java │ ├── replicate │ ├── ReplicateDemandServiceTests.java │ └── ReplicateRecoveryServiceTests.java │ ├── result │ └── ResultServiceTests.java │ ├── sgx │ └── SgxServiceTests.java │ ├── sms │ └── SmsServiceTests.java │ ├── task │ ├── TaskManagerServiceTests.java │ └── TaskNotificationServiceTests.java │ ├── tee │ ├── TeeServiceMock.java │ ├── TeeServiceTests.java │ ├── TeeServicesManagerTests.java │ ├── TeeServicesPropertiesServiceTests.java │ ├── gramine │ │ └── TeeGramineServiceTests.java │ └── scone │ │ ├── LasServiceTests.java │ │ ├── LasServicesManagerTests.java │ │ └── TeeSconeServiceTests.java │ ├── utils │ └── MaxSizeHashMapTests.java │ ├── version │ └── VersionControllerTests.java │ └── worker │ └── WorkerServiceTests.java └── resources ├── iExec-RLC-RLC-icon.png ├── logback.xml ├── tmp └── test-worker │ ├── bytes32 │ └── output │ │ └── iexec_out │ │ └── determinism.iexec │ ├── callback-directory │ └── output │ │ └── computed.json │ ├── callback-fake │ └── output │ │ └── iexec_out │ │ └── callback.iexec │ ├── callback-no-data │ └── output │ │ └── computed.json │ ├── callback │ └── output │ │ └── iexec_out │ │ └── callback.iexec │ ├── deterministic-output-directory-missing │ └── output │ │ └── computed.json │ ├── deterministic-output-directory │ └── output │ │ ├── computed.json │ │ └── iexec_out │ │ ├── computed.json │ │ ├── result1.txt │ │ └── result2.txt │ ├── deterministic-output-file │ └── output │ │ ├── computed.json │ │ └── iexec_out │ │ ├── computed.json │ │ └── computing-trace.txt │ ├── notBytes32 │ └── output │ │ └── iexec_out │ │ └── determinism.iexec │ ├── scone-tee-corrupted-file │ └── output │ │ └── iexec_out │ │ └── enclaveSig.iexec │ └── scone-tee │ └── output │ └── iexec_out │ └── enclaveSig.iexec └── wiremock └── mappings ├── config-server.json └── worker-config.json /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | /out/ 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | 28 | /bin/ 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2020 IEXEC BLOCKCHAIN TECH 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17.0.13_11-jre-focal 2 | 3 | ARG jar 4 | 5 | RUN test -n "$jar" 6 | 7 | RUN apt-get update \ 8 | && apt-get install -y curl \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | COPY $jar iexec-worker.jar 12 | 13 | ENTRYPOINT [ "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "iexec-worker.jar" ] 14 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('global-jenkins-library@2.7.4') _ 2 | buildJavaProject( 3 | shouldPublishJars: true, 4 | shouldPublishDockerImages: true) 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | chain: 4 | image: docker-regis.iex.ec/poco-chain:1.0.0-poco-v5.5.0-voucher-v1.0.0-nethermind 5 | expose: 6 | - "8545" 7 | 8 | core: 9 | image: wiremock/wiremock:3.3.1 10 | expose: 11 | - "8080" 12 | volumes: 13 | - "./src/test/resources/wiremock/mappings:/home/wiremock/mappings" 14 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=9.0.0 2 | iexecCommonsPocoVersion=5.0.0 3 | iexecCommonVersion=9.0.0 4 | iexecCommonsContainersVersion=2.0.0 5 | iexecResultVersion=9.0.0 6 | iexecSmsVersion=9.0.0 7 | iexecCoreVersion=9.0.0 8 | nexusUser 9 | nexusPassword 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iExecBlockchainComputing/iexec-worker/8e9999859690b7687e7e2fb9f379c1438250e357/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | # This file is generated by the 'io.freefair.lombok' Gradle plugin 2 | config.stopBubbling = true 3 | lombok.addLombokGeneratedAnnotation = true 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'iexec-worker' 2 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker; 18 | 19 | import com.iexec.worker.chain.IexecHubService; 20 | import com.iexec.worker.feign.LoginService; 21 | import com.iexec.worker.replicate.ReplicateRecoveryService; 22 | import com.iexec.worker.result.ResultService; 23 | import com.iexec.worker.utils.LoggingUtils; 24 | import com.iexec.worker.worker.WorkerService; 25 | import lombok.extern.slf4j.Slf4j; 26 | import org.apache.commons.lang3.StringUtils; 27 | import org.springframework.boot.CommandLineRunner; 28 | import org.springframework.boot.SpringApplication; 29 | import org.springframework.boot.autoconfigure.SpringBootApplication; 30 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 31 | import org.springframework.context.annotation.Profile; 32 | import org.springframework.retry.annotation.EnableRetry; 33 | import org.springframework.scheduling.annotation.EnableAsync; 34 | 35 | import java.util.List; 36 | 37 | @Slf4j 38 | @EnableRetry 39 | @EnableAsync 40 | @SpringBootApplication 41 | @Profile("!test") 42 | @ConfigurationPropertiesScan 43 | public class Application implements CommandLineRunner { 44 | 45 | private final String workerWalletAddress; 46 | private final IexecHubService iexecHubService; 47 | private final LoginService loginService; 48 | private final WorkerService workerService; 49 | private final ReplicateRecoveryService replicateRecoveryService; 50 | private final ResultService resultService; 51 | 52 | public Application(String workerWalletAddress, 53 | IexecHubService iexecHubService, 54 | LoginService loginService, 55 | WorkerService workerService, 56 | ReplicateRecoveryService replicateRecoveryService, 57 | ResultService resultService) { 58 | this.workerWalletAddress = workerWalletAddress; 59 | this.iexecHubService = iexecHubService; 60 | this.loginService = loginService; 61 | this.workerService = workerService; 62 | this.replicateRecoveryService = replicateRecoveryService; 63 | this.resultService = resultService; 64 | } 65 | 66 | public static void main(String[] args) { 67 | SpringApplication.run(Application.class, args); 68 | } 69 | 70 | @Override 71 | public void run(String... args) { 72 | if (!iexecHubService.hasEnoughGas()) { 73 | String noEnoughGas = "No enough gas! please refill your wallet [walletAddress:%s]"; 74 | String formatted = String.format(noEnoughGas, workerWalletAddress); 75 | LoggingUtils.printHighlightedMessage(formatted); 76 | System.exit(0); 77 | } 78 | 79 | if (StringUtils.isEmpty(loginService.login())) { 80 | String message = "Worker wasn't able to login, stopping..."; 81 | LoggingUtils.printHighlightedMessage(message); 82 | System.exit(0); 83 | } 84 | 85 | if (!workerService.registerWorker()) { 86 | String message = "Worker couldn't be registered, stopping..."; 87 | LoggingUtils.printHighlightedMessage(message); 88 | System.exit(0); 89 | } 90 | 91 | log.info("Cool, your iexec-worker is all set!"); 92 | 93 | // recover interrupted replicates 94 | List recoveredTasks = replicateRecoveryService.recoverInterruptedReplicates(); 95 | 96 | // clean the results folder 97 | resultService.cleanUnusedResultFolders(recoveredTasks); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/PingService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker; 18 | 19 | import com.iexec.core.api.SchedulerClient; 20 | import com.iexec.worker.feign.LoginService; 21 | import com.iexec.worker.utils.AsyncUtils; 22 | import com.iexec.worker.utils.ExecutorUtils; 23 | import com.iexec.worker.worker.WorkerService; 24 | import feign.FeignException; 25 | import lombok.extern.slf4j.Slf4j; 26 | import org.apache.commons.lang3.StringUtils; 27 | import org.springframework.scheduling.annotation.Scheduled; 28 | import org.springframework.stereotype.Service; 29 | 30 | import java.time.LocalTime; 31 | import java.util.concurrent.Executor; 32 | 33 | @Slf4j 34 | @Service 35 | public class PingService { 36 | 37 | private static final int PING_RATE_IN_SECONDS = 10; 38 | 39 | private final Executor executor; 40 | private final SchedulerClient schedulerClient; 41 | private final LoginService loginService; 42 | private final WorkerService workerService; 43 | 44 | private String coreSessionId; 45 | 46 | public PingService(SchedulerClient schedulerClient, 47 | LoginService loginService, 48 | WorkerService workerService) { 49 | executor = ExecutorUtils 50 | .newSingleThreadExecutorWithFixedSizeQueue(1, "ping-"); 51 | this.schedulerClient = schedulerClient; 52 | this.loginService = loginService; 53 | this.workerService = workerService; 54 | } 55 | 56 | /** 57 | * Trigger the scheduler ping every t seconds. The method that pings the 58 | * scheduler runs asynchronously inside a new thread to liberate the thread 59 | * used for @Scheduled tasks. 60 | * We use single thread executor to make sure the worker does not ping more 61 | * than once at the same time. The executors queue is of size 1 to avoid memory 62 | * leak if the thread halts for any reason. 63 | */ 64 | @Scheduled(fixedRate = PING_RATE_IN_SECONDS * 1000) 65 | void triggerSchedulerPing() { 66 | log.debug("Triggering scheduler ping action"); 67 | AsyncUtils.runAsyncTask("ping", this::pingScheduler, executor); 68 | } 69 | 70 | /** 71 | * Send ping message to the scheduler and save the session id when pinging for 72 | * the first time. If the session id changes, it means that the scheduler has 73 | * restarted, so the worker needs to restart also. 74 | */ 75 | void pingScheduler() { 76 | log.debug("Sending ping to scheduler"); 77 | final String sessionId; 78 | try { 79 | sessionId = schedulerClient.ping(loginService.getToken()); 80 | } catch (FeignException e) { 81 | log.warn("The worker cannot ping the core [status:{}]", e.status()); 82 | if (e instanceof FeignException.Unauthorized) { 83 | loginService.login(); 84 | } 85 | return; 86 | } 87 | 88 | if (StringUtils.isEmpty(sessionId)) { 89 | log.warn("The worker cannot ping the core"); 90 | return; 91 | } 92 | 93 | // Log once in an hour, in the first ping of the first minute. 94 | LocalTime now = LocalTime.now(); 95 | if (now.getMinute() == 0 && now.getSecond() <= PING_RATE_IN_SECONDS) { 96 | log.info("Sent ping to scheduler [sessionId:{}]", sessionId); 97 | } 98 | if (StringUtils.isEmpty(coreSessionId)) { 99 | log.info("First ping from the worker, setting the sessionId [coreSessionId:{}]", sessionId); 100 | coreSessionId = sessionId; 101 | return; 102 | } 103 | if (!sessionId.equalsIgnoreCase(coreSessionId)) { 104 | // need to reconnect to the core by restarting the worker 105 | log.warn("Scheduler seems to have restarted [currentSessionId:{}, coreSessionId:{}]", 106 | coreSessionId, sessionId); 107 | workerService.restartGracefully(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/chain/Contribution.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import lombok.Builder; 20 | 21 | @Builder 22 | public record Contribution(String chainTaskId, 23 | String resultDigest, 24 | String resultHash, 25 | String resultSeal, 26 | String enclaveSignature, 27 | String workerPoolSignature, 28 | String enclaveChallenge) { 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/chain/EnclaveAuthorizationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import com.iexec.commons.poco.security.Signature; 20 | import com.iexec.commons.poco.utils.BytesUtils; 21 | import com.iexec.commons.poco.utils.EthAddress; 22 | import com.iexec.commons.poco.utils.HashUtils; 23 | import com.iexec.commons.poco.utils.SignatureUtils; 24 | import lombok.extern.slf4j.Slf4j; 25 | import org.springframework.stereotype.Service; 26 | 27 | import static com.iexec.commons.poco.utils.SignatureUtils.isExpectedSignerOnSignedMessageHash; 28 | 29 | @Slf4j 30 | @Service 31 | public class EnclaveAuthorizationService { 32 | 33 | public boolean isVerifiedEnclaveSignature(String chainTaskId, 34 | String resultHash, 35 | String resultSeal, 36 | String enclaveSignature, 37 | String enclaveChallenge) { 38 | String baseErrorMessage = 39 | "Cannot verify enclave signature with invalid "; 40 | if (!BytesUtils.isNonZeroedBytes32(resultHash)) { 41 | log.error(baseErrorMessage + "result hash [chainTaskId:{}, resultHash:{}]", 42 | chainTaskId, resultHash); 43 | return false; 44 | } 45 | if (!BytesUtils.isNonZeroedBytes32(resultSeal)) { 46 | log.error(baseErrorMessage + "result seal [chainTaskId:{}, resultSeal:{}]", 47 | chainTaskId, resultSeal); 48 | return false; 49 | } 50 | if (!SignatureUtils.isSignature(enclaveSignature)) { 51 | log.error(baseErrorMessage + "enclave signature [chainTaskId:{}, enclaveSignature:{}]", 52 | chainTaskId, enclaveSignature); 53 | return false; 54 | } 55 | if (!EthAddress.validate(enclaveChallenge)) { 56 | log.error(baseErrorMessage + "enclave challenge [chainTaskId:{}, enclaveChallenge:{}]", 57 | chainTaskId, enclaveChallenge); 58 | return false; 59 | } 60 | 61 | final String messageHash = HashUtils.concatenateAndHash(resultHash, resultSeal); 62 | 63 | return isExpectedSignerOnSignedMessageHash(messageHash, 64 | new Signature(enclaveSignature), enclaveChallenge); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/chain/WalletConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import com.iexec.commons.poco.chain.SignerService; 20 | import lombok.Value; 21 | import org.springframework.boot.context.properties.ConfigurationProperties; 22 | import org.springframework.context.annotation.Bean; 23 | 24 | @Value 25 | @ConfigurationProperties(prefix = "wallet") 26 | public class WalletConfiguration { 27 | String encryptedFilePath; 28 | String password; 29 | 30 | @Bean 31 | SignerService signerService(Web3jService web3jService) throws Exception { 32 | return new SignerService(web3jService.getWeb3j(), web3jService.getChainId(), password, encryptedFilePath); 33 | } 34 | 35 | @Bean 36 | public String workerWalletAddress(SignerService signerService) { 37 | return signerService.getAddress(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/chain/Web3jService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import com.iexec.commons.poco.chain.Web3jAbstractService; 20 | import com.iexec.worker.chain.event.LatestBlockEvent; 21 | import com.iexec.worker.config.ConfigServerConfigurationService; 22 | import com.iexec.worker.config.WorkerConfigurationService; 23 | import org.springframework.context.event.EventListener; 24 | import org.springframework.stereotype.Service; 25 | 26 | @Service 27 | public class Web3jService extends Web3jAbstractService { 28 | 29 | private long latestBlockNumber; 30 | 31 | public Web3jService(ConfigServerConfigurationService configServerConfigurationService, 32 | WorkerConfigurationService workerConfService) { 33 | super( 34 | configServerConfigurationService.getChainId(), 35 | !workerConfService.getOverrideBlockchainNodeAddress().isEmpty() ? 36 | workerConfService.getOverrideBlockchainNodeAddress() : 37 | configServerConfigurationService.getChainNodeUrl(), 38 | configServerConfigurationService.getBlockTime(), 39 | workerConfService.getGasPriceMultiplier(), 40 | workerConfService.getGasPriceCap(), 41 | configServerConfigurationService.isSidechain() 42 | ); 43 | } 44 | 45 | @EventListener 46 | private void onLatestBlockEvent(final LatestBlockEvent event) { 47 | this.latestBlockNumber = event.getBlockNumber(); 48 | } 49 | 50 | @Override 51 | public long getLatestBlockNumber() { 52 | return latestBlockNumber; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/chain/event/LatestBlockEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain.event; 18 | 19 | import lombok.EqualsAndHashCode; 20 | import lombok.Value; 21 | import org.springframework.context.ApplicationEvent; 22 | 23 | @Value 24 | @EqualsAndHashCode(callSuper = true) 25 | public class LatestBlockEvent extends ApplicationEvent { 26 | long blockNumber; 27 | String blockHash; 28 | long blockTimestamp; 29 | 30 | public LatestBlockEvent(final Object source, final long blockNumber, final String blockHash, final long blockTimestamp) { 31 | super(source); 32 | this.blockNumber = blockNumber; 33 | this.blockHash = blockHash; 34 | this.blockTimestamp = blockTimestamp; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/ComputeController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute; 18 | 19 | 20 | import com.iexec.common.result.ComputedFile; 21 | import com.iexec.common.worker.api.ExitMessage; 22 | import com.iexec.worker.chain.WorkerpoolAuthorizationService; 23 | import com.iexec.worker.result.ResultService; 24 | import org.springframework.http.HttpStatus; 25 | import org.springframework.http.ResponseEntity; 26 | import org.springframework.web.bind.annotation.*; 27 | 28 | import java.util.NoSuchElementException; 29 | 30 | import static org.springframework.http.ResponseEntity.ok; 31 | 32 | @RestController 33 | public class ComputeController { 34 | 35 | private final ComputeExitCauseService computeStageExitService; 36 | private final ResultService resultService; 37 | private final WorkerpoolAuthorizationService workerpoolAuthorizationService; 38 | 39 | public ComputeController(final ComputeExitCauseService computeStageExitService, 40 | final ResultService resultService, 41 | final WorkerpoolAuthorizationService workerpoolAuthorizationService) { 42 | this.computeStageExitService = computeStageExitService; 43 | this.resultService = resultService; 44 | this.workerpoolAuthorizationService = workerpoolAuthorizationService; 45 | } 46 | 47 | @PostMapping("/compute/{stage}/{chainTaskId}/exit") 48 | public ResponseEntity sendExitCauseForGivenComputeStage( 49 | @RequestHeader("Authorization") String authorization, 50 | @PathVariable ComputeStage stage, 51 | @PathVariable String chainTaskId, 52 | @RequestBody ExitMessage exitMessage) { 53 | try { 54 | if (!workerpoolAuthorizationService.isSignedWithEnclaveChallenge(chainTaskId, authorization)) { 55 | return ResponseEntity 56 | .status(HttpStatus.UNAUTHORIZED.value()) 57 | .build(); 58 | } 59 | } catch (NoSuchElementException e) { 60 | return ResponseEntity 61 | .status(HttpStatus.NOT_FOUND.value()) 62 | .build(); 63 | } 64 | 65 | if (exitMessage.cause() == null) { 66 | return ResponseEntity 67 | .status(HttpStatus.BAD_REQUEST.value()) 68 | .build(); 69 | } 70 | if (!computeStageExitService.setExitCause(stage, 71 | chainTaskId, 72 | exitMessage.cause())) { 73 | return ResponseEntity 74 | .status(HttpStatus.ALREADY_REPORTED.value()) 75 | .build(); 76 | } 77 | return ok().build(); 78 | } 79 | 80 | @PostMapping(path = { 81 | "/iexec_out/{chainTaskId}/computed", //@Deprecated 82 | "/compute/" + ComputeStage.POST_VALUE + "/{chainTaskId}/computed" 83 | }) 84 | public ResponseEntity sendComputedFileForTee(@RequestHeader("Authorization") String authorization, 85 | @PathVariable String chainTaskId, 86 | @RequestBody ComputedFile computedFile) { 87 | try { 88 | if (!workerpoolAuthorizationService.isSignedWithEnclaveChallenge(chainTaskId, authorization)) { 89 | return ResponseEntity 90 | .status(HttpStatus.UNAUTHORIZED.value()) 91 | .build(); 92 | } 93 | } catch (NoSuchElementException e) { 94 | return ResponseEntity 95 | .status(HttpStatus.NOT_FOUND.value()) 96 | .build(); 97 | } 98 | if (!chainTaskId.equals(computedFile.getTaskId())) { 99 | return ResponseEntity.status(HttpStatus.BAD_REQUEST.value()).build(); 100 | } 101 | if (!resultService.writeComputedFile(computedFile)) { 102 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED.value()).build(); 103 | } 104 | return ok(chainTaskId); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/ComputeExitCauseService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.springframework.stereotype.Service; 22 | 23 | import java.util.HashMap; 24 | 25 | 26 | @Slf4j 27 | @Service 28 | public class ComputeExitCauseService { 29 | 30 | private final HashMap exitCauseMap = new HashMap<>(); 31 | 32 | /** 33 | * Report failure exit cause from pre-compute or post-compute enclave. 34 | * 35 | * @param computeStage pre-compute or post-compute-stage label 36 | * @param chainTaskId task ID 37 | * @param exitCause root cause of the failure 38 | * @return true if exit cause is reported 39 | */ 40 | boolean setExitCause(ComputeStage computeStage, 41 | String chainTaskId, 42 | ReplicateStatusCause exitCause) { 43 | String key = buildKey(computeStage, chainTaskId); 44 | if (exitCauseMap.containsKey(key)) { 45 | log.info("Cannot set exit cause since already set " + 46 | "[computeStage:{}, chainTaskId:{}, exitCause:{}]", 47 | computeStage, chainTaskId, exitCause); 48 | return false; 49 | } 50 | exitCauseMap.put(key, exitCause); 51 | log.info("Added exit cause [computeStage:{}, chainTaskId:{}, exitCause:{}]", 52 | computeStage, chainTaskId, exitCause); 53 | return true; 54 | } 55 | 56 | /** 57 | * Get exit cause for pre-compute or post-compute enclave. 58 | * 59 | * @param chainTaskId task ID 60 | * @return exit cause 61 | */ 62 | ReplicateStatusCause getReplicateStatusCause(ComputeStage computeStage, 63 | String chainTaskId) { 64 | return exitCauseMap.get(buildKey(computeStage, chainTaskId)); 65 | } 66 | 67 | /** 68 | * Get pre-compute exit cause. 69 | * 70 | * @param chainTaskId task ID 71 | * @return exit cause 72 | */ 73 | public ReplicateStatusCause getPreComputeExitCauseAndPrune(String chainTaskId) { 74 | ComputeStage stage = ComputeStage.PRE; 75 | ReplicateStatusCause cause = getReplicateStatusCause(stage, chainTaskId); 76 | pruneExitCause(stage, chainTaskId); 77 | return cause != null ? cause : ReplicateStatusCause.PRE_COMPUTE_FAILED_UNKNOWN_ISSUE; 78 | } 79 | 80 | /** 81 | * Get post-compute exit cause. 82 | * 83 | * @param chainTaskId task ID 84 | * @return exit cause 85 | */ 86 | public ReplicateStatusCause getPostComputeExitCauseAndPrune(String chainTaskId) { 87 | ComputeStage stage = ComputeStage.POST; 88 | ReplicateStatusCause cause = getReplicateStatusCause(stage, chainTaskId); 89 | pruneExitCause(stage, chainTaskId); 90 | return cause != null ? cause : ReplicateStatusCause.POST_COMPUTE_FAILED_UNKNOWN_ISSUE; 91 | } 92 | 93 | /** 94 | * Build key for storing or retrieving an exit cause 95 | * 96 | * @param prefix compute stage prefix 97 | * @param chainTaskId task ID 98 | * @return exit cause storage key 99 | */ 100 | private String buildKey(ComputeStage prefix, String chainTaskId) { 101 | return prefix + "_" + chainTaskId; 102 | } 103 | 104 | /** 105 | * Prune exit cause. 106 | * 107 | * @param computeStage compute stage 108 | * @param chainTaskId task ID 109 | */ 110 | private void pruneExitCause(ComputeStage computeStage, String chainTaskId) { 111 | exitCauseMap.remove(buildKey(computeStage, chainTaskId)); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/ComputeResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | 21 | public interface ComputeResponse { 22 | 23 | ReplicateStatusCause getExitCause(); 24 | String getStdout(); 25 | String getStderr(); 26 | default boolean isSuccessful() { 27 | return getExitCause() == null; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/ComputeStage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute; 18 | 19 | public enum ComputeStage { 20 | 21 | PRE(ComputeStage.PRE_VALUE), 22 | POST(ComputeStage.POST_VALUE); 23 | 24 | public static final String PRE_VALUE = "pre"; 25 | public static final String POST_VALUE = "post"; 26 | 27 | private final String value; 28 | 29 | ComputeStage(String value) { 30 | this.value = value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/ComputeStageConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute; 18 | 19 | import org.springframework.core.convert.converter.Converter; 20 | import org.springframework.stereotype.Component; 21 | 22 | /** 23 | * This class is needed to have a case-insensitive `stage` path variable in 24 | * {@link ComputeController#sendExitCauseForGivenComputeStage}. 25 | */ 26 | @Component 27 | public class ComputeStageConverter implements Converter { 28 | @Override 29 | public ComputeStage convert(String value) { 30 | return ComputeStage.valueOf(value.toUpperCase()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/app/AppComputeResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute.app; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import com.iexec.worker.compute.ComputeResponse; 21 | import lombok.*; 22 | 23 | @Data 24 | @Builder 25 | @Getter 26 | @NoArgsConstructor 27 | @AllArgsConstructor 28 | public class AppComputeResponse implements ComputeResponse { 29 | 30 | private ReplicateStatusCause exitCause; 31 | private String stdout; 32 | private String stderr; 33 | private int exitCode; 34 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/post/PostComputeResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute.post; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import com.iexec.worker.compute.ComputeResponse; 21 | import lombok.*; 22 | 23 | @Data 24 | @Builder 25 | @Getter 26 | @NoArgsConstructor 27 | @AllArgsConstructor 28 | public class PostComputeResponse implements ComputeResponse { 29 | 30 | private ReplicateStatusCause exitCause; 31 | private String stdout; 32 | private String stderr; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/compute/pre/PreComputeResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute.pre; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import com.iexec.sms.api.TeeSessionGenerationResponse; 21 | import com.iexec.worker.compute.ComputeResponse; 22 | import lombok.*; 23 | 24 | @Data 25 | @Builder 26 | @Getter 27 | @NoArgsConstructor 28 | @AllArgsConstructor 29 | public class PreComputeResponse implements ComputeResponse { 30 | 31 | private ReplicateStatusCause exitCause; 32 | private boolean isTeeTask; 33 | private TeeSessionGenerationResponse secureSession; 34 | private String stdout; 35 | private String stderr; 36 | 37 | 38 | @Override 39 | public boolean isSuccessful() { 40 | if (isTeeTask) { 41 | return ComputeResponse.super.isSuccessful() && secureSession != null; 42 | } 43 | return ComputeResponse.super.isSuccessful(); 44 | } 45 | 46 | public void setExitCause(ReplicateStatusCause exitCause) { 47 | this.exitCause = exitCause; 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/ConfigServerConfigurationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import com.iexec.common.config.ConfigServerClient; 20 | import com.iexec.common.config.PublicChainConfig; 21 | import lombok.extern.slf4j.Slf4j; 22 | import org.springframework.stereotype.Service; 23 | 24 | import java.time.Duration; 25 | 26 | /** 27 | * This service retrieves a bunch of configuration values related to the chain. 28 | * They are retrieved only when the instance is built and never updated. 29 | * A restart is then needed to get fresh remote values. 30 | */ 31 | @Slf4j 32 | @Service 33 | public class ConfigServerConfigurationService { 34 | private final PublicChainConfig publicChainConfig; 35 | 36 | public ConfigServerConfigurationService(ConfigServerClient configServerClient) { 37 | this.publicChainConfig = configServerClient.getPublicChainConfig(); 38 | if (publicChainConfig == null) { 39 | throw new MissingConfigurationException( 40 | "Received public chain config is null; can't create ConfigServerConfigurationService"); 41 | } 42 | 43 | log.info("Received public chain config [config:{}]", this.publicChainConfig); 44 | } 45 | 46 | public Integer getChainId() { 47 | return publicChainConfig.getChainId(); 48 | } 49 | 50 | public boolean isSidechain() { 51 | return publicChainConfig.isSidechain(); 52 | } 53 | 54 | public String getChainNodeUrl() { 55 | return publicChainConfig.getChainNodeUrl(); 56 | } 57 | 58 | public String getIexecHubContractAddress() { 59 | return publicChainConfig.getIexecHubContractAddress(); 60 | } 61 | 62 | public Duration getBlockTime() { 63 | return publicChainConfig.getBlockTime(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/MissingConfigurationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | public class MissingConfigurationException extends RuntimeException { 20 | public MissingConfigurationException(String message) { 21 | super(message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/PublicConfigurationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import com.iexec.common.config.ConfigServerClient; 20 | import com.iexec.common.config.ConfigServerClientBuilder; 21 | import com.iexec.core.api.SchedulerClient; 22 | import com.iexec.core.config.PublicConfiguration; 23 | import com.iexec.resultproxy.api.ResultProxyClient; 24 | import com.iexec.resultproxy.api.ResultProxyClientBuilder; 25 | import feign.Logger; 26 | import lombok.Getter; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.apache.commons.lang3.StringUtils; 29 | import org.springframework.context.annotation.Bean; 30 | import org.springframework.stereotype.Service; 31 | 32 | @Slf4j 33 | @Getter 34 | @Service 35 | public class PublicConfigurationService { 36 | 37 | private final PublicConfiguration publicConfiguration; 38 | 39 | public PublicConfigurationService(SchedulerClient schedulerClient) { 40 | this.publicConfiguration = schedulerClient.getPublicConfiguration(); 41 | } 42 | 43 | public String getSchedulerPublicAddress() { 44 | return publicConfiguration.getSchedulerPublicAddress(); 45 | } 46 | 47 | public String getRequiredWorkerVersion() { 48 | return publicConfiguration.getRequiredWorkerVersion(); 49 | } 50 | 51 | @Bean 52 | public ConfigServerClient configServerClient() { 53 | final String configServerURL = publicConfiguration.getConfigServerUrl(); 54 | log.debug("config-server url [url:{}]", configServerURL); 55 | return ConfigServerClientBuilder.getInstance( 56 | Logger.Level.NONE, 57 | configServerURL); 58 | } 59 | 60 | public ResultProxyClient createResultProxyClientFromURL(String url) { 61 | final boolean useDefaultURL = StringUtils.isBlank(url); 62 | final String resultProxyClientURL = useDefaultURL ? publicConfiguration.getResultRepositoryURL() : url; 63 | log.debug("result-proxy URL [url:{}, default-url:{}]", resultProxyClientURL, useDefaultURL); 64 | return ResultProxyClientBuilder.getInstance(Logger.Level.NONE, resultProxyClientURL); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/PurgeConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.iexec.worker.config; 2 | 3 | import com.iexec.common.lifecycle.purge.PurgeService; 4 | import com.iexec.common.lifecycle.purge.Purgeable; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import java.util.List; 9 | 10 | @Configuration 11 | public class PurgeConfiguration { 12 | /** 13 | * Creates a {@link PurgeService} bean, with a list of all {@link Purgeable} beans as a parameter. 14 | *

15 | * If no {@link Purgeable} bean is known, then an empty list is passed as a parameter. 16 | * This is a special case of Spring IoC, please see 17 | * Spring documentation. 18 | * @param purgeableServices List of services that can be purged on a task completion 19 | * @return An instance of {@link PurgeService} containing a list of all {@link Purgeable} beans. 20 | */ 21 | @Bean 22 | PurgeService purgeService(List purgeableServices) { 23 | return new PurgeService(purgeableServices); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/SchedulerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import com.iexec.core.api.SchedulerClient; 20 | import com.iexec.core.api.SchedulerClientBuilder; 21 | import feign.Logger; 22 | import jakarta.annotation.PostConstruct; 23 | import jakarta.validation.constraints.NotEmpty; 24 | import lombok.Value; 25 | import org.apache.commons.lang3.StringUtils; 26 | import org.hibernate.validator.constraints.URL; 27 | import org.springframework.boot.context.properties.ConfigurationProperties; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.validation.annotation.Validated; 30 | 31 | @Value 32 | @Validated 33 | @ConfigurationProperties(prefix = "core") 34 | public class SchedulerConfiguration { 35 | 36 | @URL(message = "URL must be a valid URL") 37 | @NotEmpty(message = "URL must not be empty") 38 | String url; 39 | String poolAddress; 40 | 41 | @PostConstruct 42 | private void postConstruct() { 43 | if (StringUtils.isEmpty(poolAddress) || poolAddress.equalsIgnoreCase("0x0")) { 44 | throw new MissingConfigurationException( 45 | "The workerpool address must be filled in"); 46 | } 47 | } 48 | 49 | @Bean 50 | SchedulerClient schedulerClient() { 51 | return SchedulerClientBuilder.getInstance(Logger.Level.FULL, url); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/SchedulingConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.scheduling.annotation.EnableScheduling; 21 | import org.springframework.scheduling.annotation.SchedulingConfigurer; 22 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 23 | import org.springframework.scheduling.config.ScheduledTaskRegistrar; 24 | 25 | /** 26 | * Enable Spring's {@code @Scheduled} annotation support and 27 | * use a dedicated TaskScheduler to trigger {@code @Scheduled} tasks. 28 | */ 29 | @Configuration 30 | @EnableScheduling 31 | public class SchedulingConfig implements SchedulingConfigurer { 32 | 33 | @Override 34 | public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { 35 | ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); 36 | // Only one thread is used to trigger all @Scheduled tasks. Each task should 37 | // guarantee that it does not block this single thread and that it liberates 38 | // it as soon as possible otherwise other scheduled tasks cannot be triggered. 39 | scheduler.setPoolSize(1); 40 | // Makes thread dumps more readable 41 | scheduler.setThreadNamePrefix("Scheduled-"); 42 | scheduler.initialize(); 43 | taskRegistrar.setTaskScheduler(scheduler); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/config/StompClientConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Configuration; 21 | import org.springframework.messaging.converter.MappingJackson2MessageConverter; 22 | import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; 23 | import org.springframework.web.client.RestTemplate; 24 | import org.springframework.web.socket.client.WebSocketClient; 25 | import org.springframework.web.socket.client.standard.StandardWebSocketClient; 26 | import org.springframework.web.socket.messaging.WebSocketStompClient; 27 | import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport; 28 | import org.springframework.web.socket.sockjs.client.SockJsClient; 29 | import org.springframework.web.socket.sockjs.client.Transport; 30 | import org.springframework.web.socket.sockjs.client.WebSocketTransport; 31 | 32 | import java.util.List; 33 | 34 | @Configuration 35 | public class StompClientConfiguration { 36 | @Bean 37 | WebSocketStompClient stompClient(RestTemplate restTemplate) { 38 | final WebSocketClient webSocketClient = new StandardWebSocketClient(); 39 | final List webSocketTransports = List.of( 40 | new WebSocketTransport(webSocketClient), 41 | new RestTemplateXhrTransport(restTemplate) 42 | ); 43 | final SockJsClient sockJsClient = new SockJsClient(webSocketTransports); 44 | // without SockJS: new WebSocketStompClient(webSocketClient) 45 | final WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient); 46 | stompClient.setAutoStartup(true); 47 | stompClient.setMessageConverter(new MappingJackson2MessageConverter()); 48 | stompClient.setTaskScheduler(new ConcurrentTaskScheduler()); 49 | 50 | return stompClient; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/docker/DockerRegistryConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.docker; 18 | 19 | import jakarta.annotation.PostConstruct; 20 | import lombok.Getter; 21 | import lombok.Setter; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.apache.commons.lang3.StringUtils; 24 | import org.hibernate.validator.constraints.time.DurationMin; 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.boot.context.properties.ConfigurationProperties; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | import java.time.Duration; 30 | import java.util.List; 31 | import java.util.Optional; 32 | 33 | @Slf4j 34 | @Configuration 35 | @ConfigurationProperties(prefix = "docker") 36 | @Getter 37 | @Setter 38 | public class DockerRegistryConfiguration { 39 | 40 | private List registries; 41 | 42 | /** 43 | * Min pull timeout expressed in minutes 44 | */ 45 | @DurationMin(minutes = 0) 46 | @Value("${docker.image.pull-timeout.min}") 47 | private Duration minPullTimeout; 48 | /** 49 | * Max pull timeout expressed in minutes 50 | */ 51 | @DurationMin(minutes = 0) 52 | @Value("${docker.image.pull-timeout.max}") 53 | private Duration maxPullTimeout; 54 | 55 | /** 56 | * Check that if a Docker registry's username is present, then its password is also 57 | * present, otherwise the worker will fail to start. 58 | */ 59 | @PostConstruct 60 | void validateRegistries() { 61 | if (registries == null || registries.isEmpty()) { 62 | log.warn("Docker registry list is empty"); 63 | return; 64 | } 65 | List registriesWithMissingPasswords = registries.stream() 66 | // get registries with usernames 67 | .filter(registryAuth -> StringUtils.isNotBlank(registryAuth.getAddress()) 68 | && StringUtils.isNotBlank(registryAuth.getUsername())) 69 | // from those registries get the ones where the password is missing 70 | .filter(registryAuth -> StringUtils.isBlank(registryAuth.getPassword())) 71 | .toList(); 72 | if (!registriesWithMissingPasswords.isEmpty()) { 73 | throw new IllegalArgumentException("Missing passwords for registries with usernames: " 74 | + registriesWithMissingPasswords); 75 | } 76 | } 77 | 78 | /** 79 | * Get Docker username and password for a given registry address. 80 | * 81 | * @param registryAddress address of the registry (docker.io, 82 | * mcr.microsoft.com, ecr.us-east-2.amazonaws.com) 83 | * @return auth for the registry 84 | */ 85 | public Optional getRegistryCredentials(String registryAddress) { 86 | if (StringUtils.isEmpty(registryAddress) || getRegistries() == null) { 87 | return Optional.empty(); 88 | } 89 | return getRegistries().stream() 90 | .filter(registryAuth -> registryAddress.equals(registryAuth.getAddress()) 91 | && StringUtils.isNotBlank(registryAuth.getUsername()) 92 | && StringUtils.isNotBlank(registryAuth.getPassword()) 93 | ) 94 | .findFirst(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/docker/RegistryCredentials.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.docker; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | @Data 25 | @Builder 26 | @NoArgsConstructor 27 | @AllArgsConstructor 28 | public class RegistryCredentials { 29 | 30 | private String address; 31 | private String username; 32 | private String password; 33 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/feign/BaseFeignClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.feign; 18 | 19 | import feign.FeignException; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.springframework.http.HttpStatus; 22 | 23 | import java.util.function.Function; 24 | 25 | @Slf4j 26 | public abstract class BaseFeignClient { 27 | 28 | /** 29 | * This is a generic functional interface to define an HTTP call. 30 | * T: type of the response body. It can be Void. 31 | * Object[]: array of the call arguments 32 | */ 33 | public interface HttpCall extends Function { 34 | } 35 | 36 | /* 37 | * This method should be overridden in 38 | * the subclass to define the login logic. 39 | */ 40 | abstract String login(); 41 | 42 | /* 43 | * Retry configuration values (max attempts, back off delay...) 44 | */ 45 | private static final int MAX_ATTEMPTS = 3; 46 | private static final int BACK_OFF_DELAY = 2000; // 2s 47 | 48 | /* 49 | * Generic method to make http calls. 50 | * If "infiniteRetry" is true or the status of the call is -1 51 | * the method will retry infinitely until it gets a valid 52 | * response. 53 | */ 54 | T makeHttpCall(HttpCall call, String action, String jwtToken, T defaultValueOnRepeatedFailures) { 55 | int attempt = 0; 56 | int status = -1; 57 | 58 | while (shouldRetry(attempt, status)) { 59 | try { 60 | return call.apply(jwtToken); 61 | } catch (FeignException e) { 62 | status = e.status(); 63 | 64 | final boolean containsJwt = jwtToken != null; 65 | if (e instanceof FeignException.Unauthorized && containsJwt) { 66 | // login and update token for the next call 67 | jwtToken = login(); 68 | } 69 | } 70 | 71 | attempt++; 72 | log.error("Failed to make http call [action:{}, status:{}, attempt:{}]", 73 | action, toHttpStatus(status), attempt); 74 | sleep(BACK_OFF_DELAY); 75 | } 76 | 77 | log.error("Failed to make http call [action:{}, status:{}, attempts:{}]", 78 | action, toHttpStatus(status), attempt); 79 | return defaultValueOnRepeatedFailures; 80 | } 81 | 82 | private boolean shouldRetry(int attempt, int status) { 83 | return attempt < MAX_ATTEMPTS || status < 0; 84 | } 85 | 86 | private String toHttpStatus(int status) { 87 | if (status <= 0) { 88 | return String.valueOf(status); 89 | } 90 | 91 | return HttpStatus.valueOf(status).toString(); 92 | } 93 | 94 | private void sleep(int millis) { 95 | try { 96 | Thread.sleep(millis); 97 | } catch (InterruptedException e) { 98 | log.error("Thread has been interrupted while sleeping", e); 99 | Thread.currentThread().interrupt(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/feign/CustomCoreFeignClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.feign; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusUpdate; 20 | import com.iexec.core.api.SchedulerClient; 21 | import com.iexec.core.config.WorkerModel; 22 | import com.iexec.core.notification.TaskNotification; 23 | import com.iexec.core.notification.TaskNotificationType; 24 | import com.iexec.core.replicate.ReplicateTaskSummary; 25 | import feign.FeignException; 26 | import lombok.extern.slf4j.Slf4j; 27 | import org.springframework.stereotype.Service; 28 | 29 | import java.util.Collections; 30 | import java.util.List; 31 | import java.util.Optional; 32 | 33 | @Slf4j 34 | @Service 35 | public class CustomCoreFeignClient extends BaseFeignClient { 36 | 37 | private final LoginService loginService; 38 | private final SchedulerClient coreClient; 39 | 40 | public CustomCoreFeignClient(SchedulerClient coreClient, LoginService loginService) { 41 | this.loginService = loginService; 42 | this.coreClient = coreClient; 43 | } 44 | 45 | /** 46 | * Log in the Scheduler. 47 | * Caution: this is NOT thread-safe. 48 | */ 49 | @Override 50 | String login() { 51 | return loginService.login(); 52 | } 53 | 54 | /* 55 | * How does it work? 56 | * We create an HttpCall, T being the type of the response 57 | * body which can be Void. We send it along with the arguments 58 | * to the generic "makeHttpCall()" method. If the call was 59 | * successful, we return a ResponseEntity with the response 60 | * body, otherwise, we return a ResponseEntity with the call's failure 61 | * status. 62 | * 63 | * How to pass call args? 64 | * We put call params in a Map (see below) 65 | * and we pass the Map as an argument to the lambda expression. 66 | * Inside the lambda expression we cast the arguments into their 67 | * original types required by the method to be called. 68 | * (Casting arguments is safe). 69 | */ 70 | 71 | public String getCoreVersion() { 72 | return makeHttpCall( 73 | jwtToken -> coreClient.getCoreVersion(), 74 | "getCoreVersion", null, null); 75 | } 76 | 77 | //TODO: Make registerWorker return Worker 78 | public void registerWorker(WorkerModel model) { 79 | makeHttpCall( 80 | jwtToken -> coreClient.registerWorker(jwtToken, model), 81 | "registerWorker", loginService.getToken(), null); 82 | } 83 | 84 | public List getComputingTasks() { 85 | return makeHttpCall( 86 | coreClient::getComputingTasks, 87 | "getComputingTasks", loginService.getToken(), Collections.emptyList()); 88 | } 89 | 90 | public List getMissedTaskNotifications(long lastAvailableBlockNumber) { 91 | return makeHttpCall( 92 | jwtToken -> coreClient.getMissedTaskNotifications(jwtToken, lastAvailableBlockNumber), 93 | "getMissedNotifications", loginService.getToken(), Collections.emptyList()); 94 | } 95 | 96 | public Optional getAvailableReplicateTaskSummary(long lastAvailableBlockNumber) { 97 | try { 98 | return Optional.ofNullable(coreClient.getAvailableReplicateTaskSummary( 99 | loginService.getToken(), 100 | lastAvailableBlockNumber 101 | )); 102 | } catch (FeignException e) { 103 | log.error("Failed to retrieve work from scheduler [httpStatus:{}]", e.status()); 104 | if (e instanceof FeignException.Unauthorized) { 105 | login(); 106 | } 107 | } 108 | return Optional.empty(); 109 | } 110 | 111 | public TaskNotificationType updateReplicateStatus(String chainTaskId, ReplicateStatusUpdate replicateStatusUpdate) { 112 | try { 113 | final TaskNotificationType taskNotificationType = coreClient.updateReplicateStatus( 114 | loginService.getToken(), 115 | chainTaskId, 116 | replicateStatusUpdate 117 | ); 118 | log.info("Updated replicate status [status:{}, chainTaskId:{}]", 119 | replicateStatusUpdate.getStatus(), chainTaskId); 120 | return taskNotificationType; 121 | } catch (FeignException e) { 122 | log.error("Exception while trying to update replicate status" + 123 | " [chainTaskId:{}, statusUpdate:{}, httpStatus:{}]", 124 | chainTaskId, replicateStatusUpdate, e.status()); 125 | if (e instanceof FeignException.Unauthorized) { 126 | login(); 127 | } 128 | return null; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/feign/LoginService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.feign; 18 | 19 | import com.iexec.commons.poco.chain.SignerService; 20 | import com.iexec.commons.poco.security.Signature; 21 | import com.iexec.commons.poco.utils.SignatureUtils; 22 | import com.iexec.core.api.SchedulerClient; 23 | import feign.FeignException; 24 | import lombok.extern.slf4j.Slf4j; 25 | import org.apache.commons.lang3.StringUtils; 26 | import org.springframework.stereotype.Service; 27 | import org.web3j.crypto.ECKeyPair; 28 | 29 | import java.util.Objects; 30 | import java.util.concurrent.locks.ReentrantLock; 31 | 32 | @Slf4j 33 | @Service 34 | public class LoginService { 35 | 36 | static final String TOKEN_PREFIX = "Bearer "; 37 | private String jwtToken; 38 | 39 | private final SignerService signerService; 40 | private final SchedulerClient coreClient; 41 | private final ReentrantLock lock = new ReentrantLock(); 42 | 43 | LoginService(SignerService signerService, SchedulerClient coreClient) { 44 | this.signerService = signerService; 45 | this.coreClient = coreClient; 46 | } 47 | 48 | public String getToken() { 49 | return jwtToken; 50 | } 51 | 52 | /** 53 | * Log in the Scheduler. 54 | *

55 | * Thread safety is implemented with a {@link ReentrantLock} and a {@code try {} finally {}} block. 56 | * The lock is acquired before entering the {@code try} block. 57 | * The latter has been added to ensure the lock will always be released once acquired. 58 | * If the lock is not acquired, a login procedure is already ongoing and the method returns immediately. 59 | * 60 | * @return An authentication token 61 | */ 62 | public String login() { 63 | if (!lock.tryLock()) { 64 | log.info("login already ongoing"); 65 | return ""; 66 | } 67 | try { 68 | final String oldToken = jwtToken; 69 | expireToken(); 70 | 71 | final String workerAddress = signerService.getCredentials().getAddress(); 72 | final ECKeyPair ecKeyPair = signerService.getCredentials().getEcKeyPair(); 73 | 74 | final String challenge; 75 | try { 76 | challenge = coreClient.getChallenge(workerAddress); 77 | } catch (FeignException e) { 78 | log.error("Cannot login, failed to get challenge [status:{}]", e.status()); 79 | return ""; 80 | } 81 | if (StringUtils.isEmpty(challenge)) { 82 | log.error("Cannot login, challenge is empty [challenge:{}]", challenge); 83 | return ""; 84 | } 85 | 86 | final Signature signature = SignatureUtils.hashAndSign(challenge, ecKeyPair); 87 | final String token; 88 | try { 89 | token = coreClient.login(workerAddress, signature); 90 | } catch (FeignException e) { 91 | log.error("Cannot login, failed to get token [status:{}]", e.status()); 92 | return ""; 93 | } 94 | if (StringUtils.isEmpty(token)) { 95 | log.error("Cannot login, token is empty [token:{}]", token); 96 | return ""; 97 | } 98 | 99 | jwtToken = TOKEN_PREFIX + token; 100 | log.info("Retrieved {} JWT token from scheduler", Objects.equals(oldToken, jwtToken) ? "existing" : "new"); 101 | return jwtToken; 102 | } finally { 103 | lock.unlock(); 104 | } 105 | } 106 | 107 | private void expireToken() { 108 | jwtToken = ""; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/feign/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.feign.config; 18 | 19 | import com.iexec.worker.config.WorkerConfigurationService; 20 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 21 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 22 | import org.apache.hc.core5.http.HttpHost; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 26 | import org.springframework.web.client.RestTemplate; 27 | 28 | @Configuration 29 | public class RestTemplateConfig { 30 | 31 | private final WorkerConfigurationService workerConfService; 32 | 33 | public RestTemplateConfig(WorkerConfigurationService workerConfService) { 34 | this.workerConfService = workerConfService; 35 | } 36 | 37 | @Bean 38 | public RestTemplate restTemplate() { 39 | final CloseableHttpClient httpClient = HttpClientBuilder.create() 40 | .setProxy(getProxy()) 41 | .build(); 42 | final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); 43 | factory.setHttpClient(httpClient); 44 | return new RestTemplate(factory); 45 | } 46 | 47 | /* 48 | * TODO 49 | * Set multiple proxies 50 | * Use HttpRoutePlanner to support both http & https proxies at the same time 51 | * https://stackoverflow.com/a/34432952 52 | * */ 53 | private HttpHost getProxy() { 54 | final String httpsProxyHost = workerConfService.getHttpsProxyHost(); 55 | final Integer httpsProxyPort = workerConfService.getHttpsProxyPort(); 56 | final String httpProxyHost = workerConfService.getHttpProxyHost(); 57 | final Integer httpProxyPort = workerConfService.getHttpProxyPort(); 58 | 59 | if (httpsProxyHost != null && httpsProxyPort != null) { 60 | return new HttpHost("https", httpsProxyHost, httpsProxyPort); 61 | } else if (httpProxyHost != null && httpProxyPort != null) { 62 | return new HttpHost("http", httpProxyHost, httpProxyPort); 63 | } 64 | return null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/metric/AggregatedDurations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import lombok.Value; 20 | 21 | @Value 22 | public class AggregatedDurations { 23 | long durationSamplesCount; 24 | double minDuration; 25 | double maxDuration; 26 | double averageDuration; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/metric/ComputeDurationsConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import org.springframework.beans.factory.annotation.Value; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | 24 | @Configuration 25 | public class ComputeDurationsConfig { 26 | private final int windowSize; 27 | 28 | public ComputeDurationsConfig(@Value("${metrics.window-size}") int windowSize) { 29 | this.windowSize = windowSize; 30 | } 31 | 32 | @Bean 33 | ComputeDurationsService preComputeDurationsService(MeterRegistry registry, 34 | String workerWalletAddress) { 35 | return new ComputeDurationsService(registry, workerWalletAddress, "pre_compute", windowSize); 36 | } 37 | 38 | @Bean 39 | ComputeDurationsService appComputeDurationsService(MeterRegistry registry, 40 | String workerWalletAddress) { 41 | return new ComputeDurationsService(registry, workerWalletAddress, "app_compute", windowSize); 42 | } 43 | 44 | @Bean 45 | ComputeDurationsService postComputeDurationsService(MeterRegistry registry, 46 | String workerWalletAddress) { 47 | return new ComputeDurationsService(registry, workerWalletAddress, "post_compute", windowSize); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/metric/ComputeDurationsService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import com.iexec.worker.utils.MaxSizeHashMap; 20 | import io.micrometer.core.instrument.Gauge; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; 23 | 24 | import java.util.Collection; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | 28 | /** 29 | * Simple service to store durations of a compute stage (pre, app or post-compute) 30 | * and aggregates them (min, max and average). 31 | */ 32 | public class ComputeDurationsService { 33 | private static final String EXPORTED_STAT_PREFIX = "iexec_"; 34 | 35 | private final Map durationPerChainTaskId; 36 | private final DescriptiveStatistics statistics; 37 | 38 | public ComputeDurationsService(MeterRegistry registry, 39 | String workerWalletAddress, 40 | String context, 41 | int windowSize) { 42 | this.durationPerChainTaskId = new MaxSizeHashMap<>(windowSize); 43 | this.statistics = new DescriptiveStatistics(windowSize); 44 | 45 | final String[] tags = {"wallet", workerWalletAddress, "phase", context}; 46 | Gauge.builder(EXPORTED_STAT_PREFIX + context + "_duration_min", statistics::getMin) 47 | .tags(tags) 48 | .register(registry); 49 | Gauge.builder(EXPORTED_STAT_PREFIX + context + "_duration_max", statistics::getMax) 50 | .tags(tags) 51 | .register(registry); 52 | Gauge.builder(EXPORTED_STAT_PREFIX + context + "_duration_average", statistics::getMean) 53 | .tags(tags) 54 | .register(registry); 55 | Gauge.builder(EXPORTED_STAT_PREFIX + context + "_duration_samples_count", statistics::getN) 56 | .tags(tags) 57 | .register(registry); 58 | } 59 | 60 | /** 61 | * Stores a new duration for a task. 62 | * 63 | * @param chainTaskId Chain task id for the duration 64 | * @param duration Duration for the task 65 | */ 66 | public void addDurationForTask(String chainTaskId, long duration) { 67 | durationPerChainTaskId.put(chainTaskId, duration); 68 | statistics.addValue(duration); 69 | } 70 | 71 | /** 72 | * Returns the duration for a task. 73 | * 74 | * @param chainTaskId Chain task ID whose associated duration should be retrieved. 75 | * @return An {@link Optional} containing the duration, 76 | * {@link Optional#empty()} if no duration for given ID. 77 | */ 78 | public Optional getDurationForTask(String chainTaskId) { 79 | return Optional.ofNullable(durationPerChainTaskId.get(chainTaskId)); 80 | } 81 | 82 | /** 83 | * Returns a collection of all registered chain task IDs. 84 | * 85 | * @return A {@link Collection} of all registered chain task IDs. 86 | */ 87 | public Collection getChainTaskIds() { 88 | return durationPerChainTaskId.keySet(); 89 | } 90 | 91 | /** 92 | * Aggregates durations of all tasks and returns min, max and average values. 93 | * 94 | * @return An instance of {@link AggregatedDurations}. 95 | */ 96 | public AggregatedDurations getAggregatedDurations() { 97 | return new AggregatedDurations( 98 | statistics.getN(), 99 | statistics.getMin(), 100 | statistics.getMax(), 101 | statistics.getMean() 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/metric/MetricsController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import org.springframework.http.ResponseEntity; 20 | import org.springframework.web.bind.annotation.GetMapping; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import static org.springframework.http.ResponseEntity.ok; 25 | 26 | @RestController 27 | @RequestMapping("/metrics") 28 | public class MetricsController { 29 | 30 | private final MetricsService metricsService; 31 | 32 | public MetricsController(MetricsService metricsService) { 33 | this.metricsService = metricsService; 34 | } 35 | 36 | @GetMapping 37 | public ResponseEntity getWorkerMetrics() { 38 | return ok(metricsService.getWorkerMetrics()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/metric/MetricsService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; 20 | import org.springframework.stereotype.Service; 21 | 22 | import java.util.List; 23 | import java.util.Optional; 24 | 25 | @Service 26 | public class MetricsService { 27 | private final ComputeDurationsService preComputeDurationsService; 28 | private final ComputeDurationsService appComputeDurationsService; 29 | private final ComputeDurationsService postComputeDurationsService; 30 | 31 | public MetricsService(ComputeDurationsService preComputeDurationsService, 32 | ComputeDurationsService appComputeDurationsService, 33 | ComputeDurationsService postComputeDurationsService) { 34 | this.preComputeDurationsService = preComputeDurationsService; 35 | this.appComputeDurationsService = appComputeDurationsService; 36 | this.postComputeDurationsService = postComputeDurationsService; 37 | } 38 | 39 | public WorkerMetrics getWorkerMetrics() { 40 | return new WorkerMetrics( 41 | preComputeDurationsService.getAggregatedDurations(), 42 | appComputeDurationsService.getAggregatedDurations(), 43 | postComputeDurationsService.getAggregatedDurations(), 44 | getCompleteComputeMetrics() 45 | ); 46 | } 47 | 48 | // region Complete compute duration 49 | AggregatedDurations getCompleteComputeMetrics() { 50 | final List durations = getCompleteComputeDurations(); 51 | final DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics(); 52 | durations.forEach(descriptiveStatistics::addValue); 53 | 54 | return new AggregatedDurations( 55 | descriptiveStatistics.getN(), 56 | descriptiveStatistics.getMin(), 57 | descriptiveStatistics.getMax(), 58 | descriptiveStatistics.getMean() 59 | ); 60 | } 61 | 62 | /** 63 | * For each task whose app-compute duration is known, 64 | * computes its complete compute duration 65 | * and returns all of them as a {@link List}. 66 | */ 67 | List getCompleteComputeDurations() { 68 | return appComputeDurationsService 69 | .getChainTaskIds() 70 | .stream() 71 | .map(this::getCompleteComputeDuration) 72 | .filter(Optional::isPresent) 73 | .map(Optional::get) 74 | .toList(); 75 | } 76 | 77 | /** 78 | * Computes and returns the complete compute duration for a task. 79 | * If any of app or post-compute duration is unknown, then returns an {@link Optional#empty()}. 80 | */ 81 | Optional getCompleteComputeDuration(String chainTaskId) { 82 | final Optional preComputeDuration = preComputeDurationsService.getDurationForTask(chainTaskId); 83 | final Optional appComputeDuration = appComputeDurationsService.getDurationForTask(chainTaskId); 84 | final Optional postComputeDuration = postComputeDurationsService.getDurationForTask(chainTaskId); 85 | 86 | // Should check whether appComputeDuration is still known. 87 | // It could have been purged if max number of durations has been reached 88 | // and new durations have been added. 89 | if (appComputeDuration.isEmpty() || postComputeDuration.isEmpty()) { 90 | return Optional.empty(); 91 | } 92 | 93 | final double completeDuration = (double) 94 | preComputeDuration.orElse(0L) 95 | + appComputeDuration.get() 96 | + postComputeDuration.get(); 97 | return Optional.of(completeDuration); 98 | } 99 | // endregion 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/metric/WorkerMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import lombok.Value; 20 | 21 | @Value 22 | public class WorkerMetrics { 23 | AggregatedDurations preComputeDurations; 24 | AggregatedDurations appComputeDurations; 25 | AggregatedDurations postComputeDurations; 26 | AggregatedDurations completeComputeDurations; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/pubsub/SessionCreatedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.pubsub; 18 | 19 | import lombok.NoArgsConstructor; 20 | 21 | /** 22 | * Publish this event when a new STOMP session 23 | * is created to notify subscribers. 24 | */ 25 | @NoArgsConstructor 26 | public class SessionCreatedEvent { 27 | 28 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/pubsub/SessionLostEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.pubsub; 18 | 19 | import lombok.NoArgsConstructor; 20 | 21 | /** 22 | * Publish this event when a STOMP session 23 | * has been lost to notify subscribers. 24 | */ 25 | @NoArgsConstructor 26 | public class SessionLostEvent { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/replicate/ReplicateActionResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.replicate; 18 | 19 | import com.iexec.common.replicate.ComputeLogs; 20 | import com.iexec.common.replicate.ReplicateStatusCause; 21 | import com.iexec.common.replicate.ReplicateStatusDetails; 22 | import com.iexec.commons.poco.chain.ChainReceipt; 23 | import lombok.AllArgsConstructor; 24 | import lombok.Builder; 25 | import lombok.Data; 26 | import lombok.NoArgsConstructor; 27 | 28 | @Data 29 | @Builder 30 | @NoArgsConstructor 31 | @AllArgsConstructor 32 | public class ReplicateActionResponse { 33 | 34 | private boolean isSuccess; 35 | private ReplicateStatusDetails details; 36 | 37 | public static ReplicateActionResponse success() { 38 | return new ReplicateActionResponse(true, null); 39 | } 40 | 41 | public static ReplicateActionResponse success(ChainReceipt chainReceipt) { 42 | ReplicateStatusDetails details = ReplicateStatusDetails.builder() 43 | .chainReceipt(chainReceipt) 44 | .build(); 45 | return new ReplicateActionResponse(true, details); 46 | } 47 | 48 | public static ReplicateActionResponse success(String resultLink, String callbackData) { 49 | ReplicateStatusDetails details = ReplicateStatusDetails.builder() 50 | .resultLink(resultLink) 51 | .chainCallbackData(callbackData) 52 | .build(); 53 | return new ReplicateActionResponse(true, details); 54 | } 55 | 56 | public static ReplicateActionResponse successWithLogs(ComputeLogs computeLogs) { 57 | ReplicateStatusDetails details = ReplicateStatusDetails.builder() 58 | .computeLogs(computeLogs) 59 | .build(); 60 | return new ReplicateActionResponse(true, details); 61 | } 62 | 63 | public static ReplicateActionResponse failure() { 64 | return new ReplicateActionResponse(false, null); 65 | } 66 | 67 | public static ReplicateActionResponse failure(ReplicateStatusCause cause) { 68 | ReplicateStatusDetails details = ReplicateStatusDetails.builder() 69 | .cause(cause) 70 | .build(); 71 | return new ReplicateActionResponse(false, details); 72 | } 73 | 74 | public static ReplicateActionResponse failureWithStdout(String stdout) { 75 | ReplicateStatusDetails details = ReplicateStatusDetails.builder() 76 | .computeLogs(ComputeLogs.builder().stdout(stdout).build()) 77 | .build(); 78 | return new ReplicateActionResponse(false, details); 79 | } 80 | 81 | public static ReplicateActionResponse failureWithStdout(ReplicateStatusCause cause, String stdout) { 82 | ReplicateStatusDetails details = ReplicateStatusDetails.builder() 83 | .cause(cause) 84 | .computeLogs(ComputeLogs.builder().stdout(stdout).build()) 85 | .build(); 86 | return new ReplicateActionResponse(false, details); 87 | } 88 | 89 | public static ReplicateActionResponse failureWithDetails(ReplicateStatusDetails details) { 90 | return new ReplicateActionResponse(false, details); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/replicate/ReplicateRecoveryService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.replicate; 18 | 19 | import com.iexec.common.result.ComputedFile; 20 | import com.iexec.commons.poco.task.TaskDescription; 21 | import com.iexec.core.notification.TaskNotification; 22 | import com.iexec.core.notification.TaskNotificationType; 23 | import com.iexec.worker.chain.IexecHubService; 24 | import com.iexec.worker.feign.CustomCoreFeignClient; 25 | import com.iexec.worker.pubsub.SubscriptionService; 26 | import com.iexec.worker.result.ResultService; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.springframework.context.ApplicationEventPublisher; 29 | import org.springframework.stereotype.Service; 30 | 31 | import java.util.Collections; 32 | import java.util.List; 33 | 34 | /** 35 | * This service is used to remind the worker of possible interrupted works 36 | * after a restart and how to deal with each interruption. 37 | */ 38 | @Slf4j 39 | @Service 40 | public class ReplicateRecoveryService { 41 | 42 | private final CustomCoreFeignClient customCoreFeignClient; 43 | private final SubscriptionService subscriptionService; 44 | private final ResultService resultService; 45 | private final IexecHubService iexecHubService; 46 | private final ApplicationEventPublisher applicationEventPublisher; 47 | 48 | public ReplicateRecoveryService(CustomCoreFeignClient customCoreFeignClient, 49 | SubscriptionService subscriptionService, 50 | ResultService resultService, 51 | IexecHubService iexecHubService, 52 | ApplicationEventPublisher applicationEventPublisher) { 53 | this.customCoreFeignClient = customCoreFeignClient; 54 | this.subscriptionService = subscriptionService; 55 | this.resultService = resultService; 56 | this.iexecHubService = iexecHubService; 57 | this.applicationEventPublisher = applicationEventPublisher; 58 | } 59 | 60 | public List recoverInterruptedReplicates() { 61 | long latestAvailableBlockNumber = iexecHubService.getLatestBlockNumber(); 62 | List missedTaskNotifications = customCoreFeignClient.getMissedTaskNotifications( 63 | latestAvailableBlockNumber); 64 | 65 | if (missedTaskNotifications == null || missedTaskNotifications.isEmpty()) { 66 | log.info("No interrupted tasks to recover"); 67 | return Collections.emptyList(); 68 | } 69 | 70 | return missedTaskNotifications.stream() 71 | .filter(this::canReplicateBeRecovered) 72 | .map(TaskNotification::getChainTaskId) 73 | .toList(); 74 | } 75 | 76 | boolean canReplicateBeRecovered(TaskNotification missedTaskNotification) { 77 | final TaskNotificationType taskNotificationType = missedTaskNotification.getTaskNotificationType(); 78 | final String chainTaskId = missedTaskNotification.getChainTaskId(); 79 | final boolean isResultAvailable = resultService.isResultAvailable(chainTaskId); 80 | 81 | log.info("Recovering interrupted task [chainTaskId:{}, taskNotificationType:{}]", 82 | chainTaskId, taskNotificationType); 83 | 84 | if (!isResultAvailable) { 85 | log.error("Could not recover task, result not found [chainTaskId:{}, taskNotificationType:{}]", 86 | chainTaskId, taskNotificationType); 87 | return false; 88 | } 89 | 90 | final TaskDescription taskDescription = iexecHubService.getTaskDescription(chainTaskId); 91 | 92 | if (taskDescription == null) { 93 | log.error("Could not recover task, no TaskDescription retrieved [chainTaskId:{}, taskNotificationType:{}]", 94 | chainTaskId, taskNotificationType); 95 | return false; 96 | } 97 | 98 | final ComputedFile computedFile = resultService.getComputedFile(chainTaskId); 99 | resultService.saveResultInfo(taskDescription, computedFile); 100 | subscriptionService.subscribeToTopic(chainTaskId); 101 | applicationEventPublisher.publishEvent(missedTaskNotification); 102 | 103 | return true; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/result/ResultInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.result; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | @Data 25 | @NoArgsConstructor 26 | @AllArgsConstructor 27 | @Builder 28 | public class ResultInfo { 29 | 30 | private String image; 31 | private String tag; 32 | private String cmd; 33 | private String deterministHash; 34 | private String datasetUri; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/result/ResultUploadDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.result; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | 25 | @Data 26 | @Builder 27 | @NoArgsConstructor 28 | @AllArgsConstructor 29 | public class ResultUploadDetails { 30 | 31 | private String resultLink; 32 | private String chainCallbackData; 33 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/sms/SmsClientProviderConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.iexec.worker.sms; 2 | 3 | import com.iexec.sms.api.SmsClientProvider; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class SmsClientProviderConfiguration { 9 | @Bean 10 | SmsClientProvider smsClientProvider() { 11 | return new SmsClientProvider(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/sms/TeeSessionGenerationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.sms; 18 | 19 | import com.iexec.sms.api.TeeSessionGenerationError; 20 | 21 | public class TeeSessionGenerationException extends Exception { 22 | private final TeeSessionGenerationError teeSessionGenerationError; 23 | 24 | public TeeSessionGenerationException(TeeSessionGenerationError teeSessionGenerationError) { 25 | this.teeSessionGenerationError = teeSessionGenerationError; 26 | } 27 | 28 | public TeeSessionGenerationError getTeeSessionGenerationError() { 29 | return teeSessionGenerationError; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/tee/TeeService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import com.iexec.commons.poco.task.TaskDescription; 21 | import com.iexec.sms.api.SmsClientCreationException; 22 | import com.iexec.sms.api.TeeSessionGenerationResponse; 23 | import com.iexec.worker.sgx.SgxService; 24 | import com.iexec.worker.sms.SmsService; 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | import java.util.Collection; 28 | import java.util.List; 29 | import java.util.Optional; 30 | 31 | import static com.iexec.common.replicate.ReplicateStatusCause.*; 32 | 33 | @Slf4j 34 | public abstract class TeeService { 35 | private final SgxService sgxService; 36 | private final SmsService smsService; 37 | protected final TeeServicesPropertiesService teeServicesPropertiesService; 38 | 39 | protected TeeService(SgxService sgxService, 40 | SmsService smsService, 41 | TeeServicesPropertiesService teeServicesPropertiesService) { 42 | this.sgxService = sgxService; 43 | this.smsService = smsService; 44 | this.teeServicesPropertiesService = teeServicesPropertiesService; 45 | } 46 | 47 | public boolean isTeeEnabled() { 48 | return sgxService.isSgxEnabled(); 49 | } 50 | 51 | public Optional areTeePrerequisitesMetForTask(String chainTaskId) { 52 | if (!isTeeEnabled()) { 53 | return Optional.of(TEE_NOT_SUPPORTED); 54 | } 55 | 56 | try { 57 | // Try to load the `SmsClient` relative to the task. 58 | // If it can't be loaded, then we won't be able to run the task. 59 | smsService.getSmsClient(chainTaskId); 60 | } catch (SmsClientCreationException e) { 61 | log.error("Couldn't get SmsClient [chainTaskId: {}]", chainTaskId, e); 62 | return Optional.of(UNKNOWN_SMS); 63 | } 64 | try { 65 | // Try to load the `TeeServicesProperties` relative to the task. 66 | // If it can't be loaded, then we won't be able to run the task. 67 | teeServicesPropertiesService.getTeeServicesProperties(chainTaskId); 68 | } catch (NullPointerException e) { 69 | log.error("TEE enclave configuration is null [chainTaskId: {}]", chainTaskId, e); 70 | return Optional.of(PRE_COMPUTE_MISSING_ENCLAVE_CONFIGURATION); 71 | } catch (RuntimeException e) { 72 | log.error("Couldn't get TeeServicesProperties [chainTaskId: {}]", chainTaskId, e); 73 | return Optional.of(GET_TEE_SERVICES_CONFIGURATION_FAILED); 74 | } 75 | 76 | return Optional.empty(); 77 | } 78 | 79 | /** 80 | * Start any required service(s) to use TEE with selected technology for given task. 81 | * 82 | * @param chainTaskId Task whose service(s) should be started. 83 | * @return {@literal true} if all services have been correctly started, {@literal false} otherwise. 84 | */ 85 | public abstract boolean prepareTeeForTask(String chainTaskId); 86 | 87 | public abstract List buildPreComputeDockerEnv( 88 | TaskDescription taskDescription, 89 | TeeSessionGenerationResponse session); 90 | 91 | public abstract List buildComputeDockerEnv( 92 | TaskDescription taskDescription, 93 | TeeSessionGenerationResponse session); 94 | 95 | public abstract List buildPostComputeDockerEnv( 96 | TaskDescription taskDescription, 97 | TeeSessionGenerationResponse session); 98 | 99 | public abstract Collection getAdditionalBindings(); 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/tee/TeeServicesManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee; 18 | 19 | import com.iexec.commons.poco.tee.TeeFramework; 20 | import com.iexec.worker.tee.gramine.TeeGramineService; 21 | import com.iexec.worker.tee.scone.TeeSconeService; 22 | import org.springframework.stereotype.Service; 23 | 24 | @Service 25 | public class TeeServicesManager { 26 | 27 | private final TeeSconeService teeSconeService; 28 | private final TeeGramineService teeGramineService; 29 | 30 | public TeeServicesManager(TeeSconeService teeSconeService, TeeGramineService teeGramineService) { 31 | this.teeSconeService = teeSconeService; 32 | this.teeGramineService = teeGramineService; 33 | } 34 | 35 | public TeeService getTeeService(TeeFramework teeFramework) { 36 | if (teeFramework == null) { 37 | throw new IllegalArgumentException("TEE framework can't be null."); 38 | } 39 | 40 | switch (teeFramework) { 41 | case SCONE: 42 | return teeSconeService; 43 | case GRAMINE: 44 | return teeGramineService; 45 | default: 46 | throw new IllegalArgumentException("No TEE service defined for this TEE framework."); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/tee/TeeServicesPropertiesCreationException.java: -------------------------------------------------------------------------------- 1 | package com.iexec.worker.tee; 2 | 3 | public class TeeServicesPropertiesCreationException extends RuntimeException { 4 | public TeeServicesPropertiesCreationException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/tee/gramine/TeeGramineService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee.gramine; 18 | 19 | import com.iexec.commons.poco.task.TaskDescription; 20 | import com.iexec.sms.api.TeeSessionGenerationResponse; 21 | import com.iexec.worker.sgx.SgxService; 22 | import com.iexec.worker.sms.SmsService; 23 | import com.iexec.worker.tee.TeeService; 24 | import com.iexec.worker.tee.TeeServicesPropertiesService; 25 | import org.springframework.stereotype.Service; 26 | 27 | import java.util.ArrayList; 28 | import java.util.Collection; 29 | import java.util.List; 30 | 31 | @Service 32 | public class TeeGramineService extends TeeService { 33 | private static final String SPS_URL_ENV_VAR = "sps"; 34 | private static final String SPS_SESSION_ENV_VAR = "session"; 35 | private static final String AESMD_SOCKET = "/var/run/aesmd/aesm.socket"; 36 | 37 | public TeeGramineService(SgxService sgxService, 38 | SmsService smsService, 39 | TeeServicesPropertiesService teeServicesPropertiesService) { 40 | super(sgxService, smsService, teeServicesPropertiesService); 41 | } 42 | 43 | @Override 44 | public boolean prepareTeeForTask(String chainTaskId) { 45 | // Nothing to do for a particular task 46 | return true; 47 | } 48 | 49 | @Override 50 | public List buildPreComputeDockerEnv( 51 | TaskDescription taskDescription, 52 | TeeSessionGenerationResponse session) { 53 | return getDockerEnv(session.getSessionId(), session.getSecretProvisioningUrl()); 54 | } 55 | 56 | @Override 57 | public List buildComputeDockerEnv( 58 | TaskDescription taskDescription, 59 | TeeSessionGenerationResponse session) { 60 | return getDockerEnv(session.getSessionId(), session.getSecretProvisioningUrl()); 61 | } 62 | 63 | @Override 64 | public List buildPostComputeDockerEnv( 65 | TaskDescription taskDescription, 66 | TeeSessionGenerationResponse session) { 67 | return getDockerEnv(session.getSessionId(), session.getSecretProvisioningUrl()); 68 | } 69 | 70 | @Override 71 | public Collection getAdditionalBindings() { 72 | final List bindings = new ArrayList<>(); 73 | bindings.add(AESMD_SOCKET + ":" + AESMD_SOCKET); 74 | return bindings; 75 | } 76 | 77 | private List getDockerEnv(String sessionId, String spsUrl) { 78 | return List.of( 79 | SPS_URL_ENV_VAR + "=" + spsUrl, 80 | SPS_SESSION_ENV_VAR + "=" + sessionId); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/tee/scone/LasService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee.scone; 18 | 19 | import com.github.dockerjava.api.model.HostConfig; 20 | import com.iexec.commons.containers.DockerRunRequest; 21 | import com.iexec.commons.containers.DockerRunResponse; 22 | import com.iexec.commons.containers.client.DockerClientInstance; 23 | import com.iexec.worker.config.WorkerConfigurationService; 24 | import com.iexec.worker.docker.DockerService; 25 | import com.iexec.worker.sgx.SgxService; 26 | import lombok.Getter; 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | @Slf4j 30 | public class LasService { 31 | @Getter 32 | private final String containerName; 33 | private final String imageUri; 34 | @Getter 35 | private final SconeConfiguration sconeConfig; 36 | 37 | private final WorkerConfigurationService workerConfigService; 38 | private final SgxService sgxService; 39 | private final DockerService dockerService; 40 | 41 | @Getter 42 | private boolean isStarted; 43 | 44 | public LasService(String containerName, 45 | String imageUri, 46 | SconeConfiguration sconeConfig, 47 | WorkerConfigurationService workerConfigService, 48 | SgxService sgxService, 49 | DockerService dockerService) { 50 | this.containerName = containerName; 51 | this.imageUri = imageUri; 52 | this.sconeConfig = sconeConfig; 53 | this.workerConfigService = workerConfigService; 54 | this.sgxService = sgxService; 55 | this.dockerService = dockerService; 56 | } 57 | 58 | synchronized boolean start() { 59 | if (isStarted) { 60 | return true; 61 | } 62 | 63 | HostConfig hostConfig = HostConfig.newHostConfig() 64 | .withDevices(sgxService.getSgxDevices()) 65 | .withNetworkMode(workerConfigService.getDockerNetworkName()); 66 | DockerRunRequest dockerRunRequest = DockerRunRequest.builder() 67 | .hostConfig(hostConfig) 68 | .containerName(containerName) 69 | .imageUri(imageUri) 70 | // pre-compute, application & post-compute enclaves will be 71 | // able to talk to the LAS via this network 72 | .sgxDriverMode(sgxService.getSgxDriverMode()) 73 | .maxExecutionTime(0) 74 | .build(); 75 | if (!imageUri.contains(sconeConfig.getRegistry().getName())) { 76 | log.error("LAS image is not from a known registry [image:{}, registry:{}]", 77 | imageUri, sconeConfig.getRegistry().getName()); 78 | return false; 79 | } 80 | DockerClientInstance client; 81 | try { 82 | client = dockerService.getClient( 83 | sconeConfig.getRegistry().getName(), 84 | sconeConfig.getRegistry().getUsername(), 85 | sconeConfig.getRegistry().getPassword()); 86 | } catch (Exception e) { 87 | log.error("Failed to get Docker authenticated client to run LAS", e); 88 | return false; 89 | } 90 | if (client == null) { 91 | log.error("Docker client with credentials is required to enable TEE support"); 92 | return false; 93 | } 94 | if (!client.pullImage(imageUri)) { 95 | log.error("Failed to download LAS image"); 96 | return false; 97 | } 98 | 99 | DockerRunResponse dockerRunResponse = dockerService.run(dockerRunRequest); 100 | if (!dockerRunResponse.isSuccessful()) { 101 | log.error("Failed to start LAS service"); 102 | return false; 103 | } 104 | 105 | isStarted = true; 106 | return true; 107 | } 108 | 109 | /** 110 | * Tries to stop and remove this LAS instance container. 111 | * It is considered successful when the container is not present anymore 112 | * after the execution of this method. 113 | * 114 | * @return {@literal true} if the container is not present anymore, 115 | * {@literal false} otherwise. 116 | */ 117 | synchronized boolean stopAndRemoveContainer() { 118 | if (isStarted()) { 119 | final DockerClientInstance client = dockerService.getClient(); 120 | isStarted = client.stopAndRemoveContainer(containerName); 121 | } 122 | 123 | return !isStarted; 124 | } 125 | 126 | public String getUrl() { 127 | return containerName + ":" + sconeConfig.getLasPort(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/tee/scone/SconeConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee.scone; 18 | 19 | import lombok.Value; 20 | import org.springframework.boot.context.properties.ConfigurationProperties; 21 | 22 | /** 23 | * LAS: local attestation service. 24 | * Local service used to perform SGX specific operations to attest the enclave 25 | * (e.g. compute enclave measurement - MREnclave - and attest it through Intel 26 | * Attestation Service). 27 | * It must be on the same machine as the attested program/enclave. 28 | *

29 | * MREnclave: an enclave identifier, created by hashing all its 30 | * code. It guarantees that a code behaves exactly as expected. 31 | * 32 | *

33 | * The following assumes Scontain provides a single registry, 34 | * within which every LAS image is stored. 35 | * It also assumes every LAS uses the same port. 36 | */ 37 | @Value 38 | @ConfigurationProperties(prefix = "scone") 39 | public class SconeConfiguration { 40 | boolean showVersion; 41 | String logLevel; 42 | SconeRegistry registry; 43 | int lasPort; 44 | 45 | @Value 46 | public static class SconeRegistry { 47 | String name; 48 | String username; 49 | String password; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/utils/AsyncUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.utils; 18 | 19 | import lombok.AccessLevel; 20 | import lombok.NoArgsConstructor; 21 | import lombok.extern.slf4j.Slf4j; 22 | 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.Executor; 25 | 26 | @Slf4j 27 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 28 | public class AsyncUtils { 29 | 30 | /** 31 | * The default {@link CompletableFuture#runAsync(Runnable)} fails silently when an exception 32 | * is thrown in the running thread. This wrapper method adds an exception handler that logs 33 | * a custom error message as well as the exception itself. 34 | * 35 | * @param context custom identified logged in the error message 36 | * @param runnable the task to run 37 | * @param executor the executor used to run the task 38 | * @return A {@link CompletableFuture} representing the executing task. 39 | */ 40 | public static CompletableFuture runAsyncTask(String context, Runnable runnable, Executor executor) { 41 | log.debug("Running async task [context:{}]", context); 42 | return CompletableFuture 43 | .runAsync(runnable, executor) 44 | .exceptionally(error -> handleAsyncTaskError(context, error)); 45 | } 46 | 47 | /** 48 | * Print custom error message when a problem occurs in an async task. 49 | * 50 | * @param e 51 | * @return 52 | */ 53 | private static Void handleAsyncTaskError(String context, Throwable e) { 54 | if (e != null) { 55 | log.error("Error occurred in async task [context:{}]", context, e); 56 | } 57 | return null; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/utils/ExecutorUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.utils; 18 | 19 | import lombok.AccessLevel; 20 | import lombok.NoArgsConstructor; 21 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 22 | 23 | import java.util.concurrent.Executor; 24 | import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; 25 | 26 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 27 | public class ExecutorUtils { 28 | 29 | public static Executor newSingleThreadExecutorWithFixedSizeQueue(final int queueSize, final String threadNamePrefix) { 30 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 31 | executor.setCorePoolSize(1); 32 | executor.setMaxPoolSize(1); 33 | executor.setKeepAliveSeconds(0); 34 | executor.setQueueCapacity(queueSize); 35 | executor.setThreadNamePrefix(threadNamePrefix); 36 | // Discard silently when we add a task 37 | // to the already-full queue. 38 | executor.setRejectedExecutionHandler(new DiscardPolicy()); 39 | executor.initialize(); 40 | return executor; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/utils/LoggingUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.utils; 18 | 19 | import lombok.AccessLevel; 20 | import lombok.NoArgsConstructor; 21 | 22 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 23 | public class LoggingUtils { 24 | 25 | public static String getHighlightedMessage(String message) { 26 | String hashtagSequence = new String(new char[message.length()]).replace('\0', '#'); 27 | String spaceSequence = new String(new char[message.length()]).replace('\0', ' '); 28 | 29 | return "\n" + 30 | "##" + hashtagSequence + "##\n" + 31 | "# " + spaceSequence + " #\n" + 32 | "# " + message + " #\n" + 33 | "# " + spaceSequence + " #\n" + 34 | "##" + hashtagSequence + "##\n" + 35 | "\n"; 36 | } 37 | 38 | public static String getHeaderFooterHashMessage(String message) { 39 | String hashtagSequence = new String(new char[message.length()]).replace('\0', '#'); 40 | 41 | return "\n" + 42 | "##" + hashtagSequence + "##\n" + 43 | message + "\n" + 44 | "##" + hashtagSequence + "##\n" + 45 | "\n"; 46 | } 47 | 48 | public static void printHighlightedMessage(String message) { 49 | System.out.println(getHighlightedMessage(message)); 50 | } 51 | 52 | public static String prettifyDeveloperLogs(String iexecInTree, String iexecOutTree, String stdout, String stderr) { 53 | return "\n" + 54 | "#################### DEV MODE ####################\n" + 55 | "iexec_in folder\n" + 56 | "--------------------\n" + 57 | iexecInTree + "\n" + 58 | "\n" + 59 | "iexec_out folder\n" + 60 | "--------------------\n" + 61 | iexecOutTree + "\n" + 62 | "\n" + 63 | "stdout\n" + 64 | "--------------------\n" + 65 | stdout + "\n" + 66 | "\n" + 67 | "stderr\n" + 68 | "--------------------\n" + 69 | stderr + "\n" + 70 | "#################### DEV MODE ####################\n" + 71 | "\n"; 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/utils/MaxSizeHashMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.utils; 18 | 19 | import lombok.EqualsAndHashCode; 20 | 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * {@link LinkedHashMap} with max capacity. 26 | * The eldest element is removed when max size is reached 27 | * but new element(s) should be added. 28 | */ 29 | @EqualsAndHashCode(callSuper = true) 30 | public class MaxSizeHashMap extends LinkedHashMap { 31 | private final int maxSize; 32 | 33 | public MaxSizeHashMap(int maxSize) { 34 | this.maxSize = maxSize; 35 | } 36 | 37 | @Override 38 | protected boolean removeEldestEntry(Map.Entry eldest) { 39 | return size() > maxSize; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/utils/WorkflowException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.utils; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import lombok.Getter; 21 | 22 | @Getter 23 | public class WorkflowException extends Exception { 24 | 25 | private final ReplicateStatusCause replicateStatusCause; 26 | 27 | public WorkflowException(ReplicateStatusCause cause) { 28 | this(cause, cause.name()); 29 | } 30 | 31 | public WorkflowException(ReplicateStatusCause cause, String message) { 32 | super(message); 33 | this.replicateStatusCause = cause; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/iexec/worker/version/VersionController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.version; 18 | 19 | import io.micrometer.core.instrument.Gauge; 20 | import io.micrometer.core.instrument.Metrics; 21 | import jakarta.annotation.PostConstruct; 22 | import org.springframework.boot.info.BuildProperties; 23 | import org.springframework.http.ResponseEntity; 24 | import org.springframework.web.bind.annotation.GetMapping; 25 | import org.springframework.web.bind.annotation.RestController; 26 | 27 | @RestController 28 | public class VersionController { 29 | 30 | public static final String METRIC_INFO_GAUGE_NAME = "iexec.version.info"; 31 | public static final String METRIC_INFO_GAUGE_DESC = "A metric to expose version and application name."; 32 | public static final String METRIC_INFO_LABEL_APP_NAME = "iexecAppName"; 33 | public static final String METRIC_INFO_LABEL_APP_VERSION = "iexecAppVersion"; 34 | // Must be static final to avoid garbage collect and side effect on gauge 35 | public static final int METRIC_VALUE = 1; 36 | private final BuildProperties buildProperties; 37 | 38 | public VersionController(BuildProperties buildProperties) { 39 | this.buildProperties = buildProperties; 40 | } 41 | 42 | @PostConstruct 43 | void initializeGaugeVersion() { 44 | Gauge.builder(METRIC_INFO_GAUGE_NAME, METRIC_VALUE, n -> METRIC_VALUE) 45 | .description(METRIC_INFO_GAUGE_DESC) 46 | .tags(METRIC_INFO_LABEL_APP_VERSION, buildProperties.getVersion(), 47 | METRIC_INFO_LABEL_APP_NAME, buildProperties.getName()) 48 | .register(Metrics.globalRegistry); 49 | } 50 | 51 | @GetMapping("/version") 52 | public ResponseEntity getVersion() { 53 | return ResponseEntity.ok(buildProperties.getVersion()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${IEXEC_WORKER_PORT:13100} 3 | 4 | core: 5 | url: ${IEXEC_CORE_URL:http://localhost:13000} 6 | pool-address: ${POOL_ADDRESS:0x0} 7 | 8 | worker: 9 | name: ${IEXEC_WORKER_NAME:worker} 10 | worker-base-dir: ${IEXEC_WORKER_BASE_DIR:/tmp/iexec-worker} 11 | override-available-cpu-count: ${IEXEC_WORKER_OVERRIDE_AVAILABLE_CPU_COUNT:} # if 0 then fallback to default 12 | gpu-enabled: ${IEXEC_WORKER_GPU_ENABLED:false} 13 | gas-price-multiplier: ${IEXEC_GAS_PRICE_MULTIPLIER:1.3} # txs will be sent with networkGasPrice*gasPriceMultiplier, 4.0 means super fast 14 | gas-price-cap: ${IEXEC_GAS_PRICE_CAP:22000000000} #in Wei, will be used for txs if networkGasPrice*gasPriceMultiplier > gasPriceCap 15 | override-blockchain-node-address: ${IEXEC_WORKER_OVERRIDE_BLOCKCHAIN_NODE_ADDRESS:} #will use it if set, else will use the one given by the blockchain adapter 16 | developer-logger-enabled: ${IEXEC_DEVELOPER_LOGGER_ENABLED:false} 17 | tee-compute-max-heap-size-gb: ${IEXEC_WORKER_TEE_COMPUTE_MAX_HEAP_SIZE_GB:8} 18 | docker-network-name: ${IEXEC_WORKER_DOCKER_NETWORK_NAME:iexec-worker-net} 19 | 20 | docker: 21 | registries: 22 | - address: docker.io # do not update this line 23 | username: ${IEXEC_WORKER_DOCKER_REGISTRY_USERNAME_0:} 24 | password: ${IEXEC_WORKER_DOCKER_REGISTRY_PASSWORD_0:} 25 | - address: ${IEXEC_WORKER_DOCKER_REGISTRY_ADDRESS_1:} 26 | username: ${IEXEC_WORKER_DOCKER_REGISTRY_USERNAME_1:} 27 | password: ${IEXEC_WORKER_DOCKER_REGISTRY_PASSWORD_1:} 28 | image: 29 | pull-timeout: 30 | min: ${IEXEC_WORKER_DOCKER_IMAGE_MIN_PULL_TIMEOUT:PT5M} # image min pull timeout 31 | max: ${IEXEC_WORKER_DOCKER_IMAGE_MAX_PULL_TIMEOUT:PT30M} # image max pull timeout 32 | 33 | wallet: 34 | encrypted-file-path: ${IEXEC_WORKER_WALLET_PATH:./src/main/resources/wallet/encrypted-wallet_worker1.json} 35 | password: ${IEXEC_WORKER_WALLET_PASSWORD:whatever} 36 | 37 | scone: 38 | show-version: ${IEXEC_SCONE_SHOW_VERSION:true} 39 | log-level: ${IEXEC_SCONE_LOG_LEVEL:debug} 40 | registry: 41 | name: ${IEXEC_WORKER_SCONTAIN_REGISTRY_NAME:registry.scontain.com} 42 | username: ${IEXEC_WORKER_SCONTAIN_REGISTRY_USERNAME:} 43 | password: ${IEXEC_WORKER_SCONTAIN_REGISTRY_PASSWORD:} #could be a dedicated generated token 44 | las-port: ${IEXEC_LAS_PORT:18766} 45 | 46 | management: 47 | endpoint: 48 | restart: 49 | enabled: true 50 | 51 | tee: 52 | sgx: 53 | driver-mode: ${IEXEC_WORKER_SGX_DRIVER_MODE:NONE} 54 | 55 | metrics: 56 | window-size: ${IEXEC_WORKER_METRICS_WINDOW_SIZE:1000} 57 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${Ansi.YELLOW} _ _____ ______ 2 | ${Ansi.YELLOW} __/\__ (_) ____|_ _____ ___ \ \ \ \ 3 | ${Ansi.YELLOW} \ / | | _| \ \/ / _ \/ __| \ \ \ \ 4 | ${Ansi.YELLOW} /_ _\ | | |___ > < __/ (__ / / / / 5 | ${Ansi.YELLOW} \/ |_|_____/_/\_\___|\___| /_/_/_/ 6 | ${Ansi.YELLOW} ========= 7 | ${Ansi.YELLOW} :: ${application.title}${application.formatted-version} built with Spring Boot${spring-boot.formatted-version} :: ${Ansi.DEFAULT} -------------------------------------------------------------------------------- /src/main/resources/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ ! -z $IEXEC_HTTP_PROXY_HOST ] && [ ! -z $IEXEC_HTTP_PROXY_PORT ]; then 3 | HTTP_PROXY_OPTIONS="-Dhttp.proxyHost=$IEXEC_HTTP_PROXY_HOST -Dhttp.proxyPort=$IEXEC_HTTP_PROXY_PORT" 4 | JAVA_OPTIONS=$HTTP_PROXY_OPTIONS 5 | fi 6 | 7 | if [ ! -z $IEXEC_HTTPS_PROXY_HOST ] && [ ! -z $IEXEC_HTTPS_PROXY_PORT ]; then 8 | HTTPS_PROXY_OPTIONS="-Dhttps.proxyHost=$IEXEC_HTTPS_PROXY_HOST -Dhttps.proxyPort=$IEXEC_HTTPS_PROXY_PORT" 9 | JAVA_OPTIONS=$HTTPS_PROXY_OPTIONS 10 | fi 11 | 12 | if [ ! -z $IEXEC_HTTP_PROXY_HOST ] && [ ! -z $IEXEC_HTTP_PROXY_PORT ] && [ ! -z $IEXEC_HTTPS_PROXY_HOST ] && [ ! -z $IEXEC_HTTPS_PROXY_PORT ]; then 13 | JAVA_OPTIONS="$HTTP_PROXY_OPTIONS $HTTPS_PROXY_OPTIONS" 14 | fi 15 | 16 | java $JAVA_OPTIONS -jar /iexec-worker.jar -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/wallet/encrypted-wallet_worker1.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "1a69b2eb604db8eba185df03ea4f5288dcbbd248", 3 | "id": "9428da60-eafb-4771-a64b-fac009946bba", 4 | "version": 3, 5 | "Crypto": { 6 | "cipher": "aes-128-ctr", 7 | "cipherparams": { 8 | "iv": "924d817ab4f4f8786c6ca6c563af4102" 9 | }, 10 | "ciphertext": "0a26992572e1cad527eca658f99f79e0a9efc784144fc0524a1279a971828a43", 11 | "kdf": "scrypt", 12 | "kdfparams": { 13 | "salt": "a59ab4d6069b677bb71473c14cd58f7af0e778f10eba6ac2d24804d2e167ffb5", 14 | "n": 131072, 15 | "dklen": 32, 16 | "p": 1, 17 | "r": 8 18 | }, 19 | "mac": "0155cebb27cd4818e8348afd1c4d84eb77303101ea0ef684ec179041fbb5fed1" 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/TestApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 22 | 23 | /** 24 | * Custom test application to avoid run method and exits in Application class 25 | */ 26 | @SpringBootApplication 27 | @ConfigurationPropertiesScan 28 | public class TestApplication { 29 | 30 | public static void main(String[] args) { 31 | SpringApplication.run(TestApplication.class, args); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/TestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker; 18 | 19 | import org.mockito.invocation.InvocationOnMock; 20 | 21 | public class TestUtils { 22 | 23 | public static class ThreadNameWrapper { 24 | public String value; 25 | } 26 | 27 | public static Object saveThreadNameThenCallRealMethodThenSleepSomeMillis( 28 | ThreadNameWrapper threadNameWrapper, 29 | InvocationOnMock invocation, int sleepDuration) throws Throwable { 30 | Object invocationResult = saveThreadNameThenCallRealMethod(threadNameWrapper, invocation); 31 | Thread.sleep(sleepDuration); 32 | return invocationResult; 33 | } 34 | 35 | public static Object saveThreadNameThenCallRealMethod( 36 | ThreadNameWrapper threadNameWrapper, InvocationOnMock invocation) 37 | throws Throwable { 38 | // Save the name of the current thread 39 | threadNameWrapper.value = Thread.currentThread().getName(); 40 | // Then call real method 41 | return invocation.callRealMethod(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/chain/WalletConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import com.iexec.common.config.ConfigServerClient; 20 | import com.iexec.commons.poco.chain.SignerService; 21 | import com.iexec.worker.config.ConfigServerConfigurationService; 22 | import com.iexec.worker.config.PublicConfigurationService; 23 | import com.iexec.worker.config.SchedulerConfiguration; 24 | import com.iexec.worker.config.WorkerConfigurationService; 25 | import org.junit.jupiter.api.Test; 26 | import org.junit.jupiter.api.io.TempDir; 27 | import org.springframework.boot.test.context.runner.ApplicationContextRunner; 28 | import org.testcontainers.containers.BindMode; 29 | import org.testcontainers.containers.GenericContainer; 30 | import org.testcontainers.junit.jupiter.Container; 31 | import org.testcontainers.junit.jupiter.Testcontainers; 32 | import org.web3j.crypto.WalletUtils; 33 | 34 | import java.io.File; 35 | 36 | import static org.assertj.core.api.Assertions.assertThat; 37 | 38 | @Testcontainers 39 | class WalletConfigurationTest { 40 | private static final int WIREMOCK_PORT = 8080; 41 | 42 | private final ApplicationContextRunner runner = new ApplicationContextRunner(); 43 | @TempDir 44 | private File tempWalletDir; 45 | 46 | @Container 47 | static final GenericContainer wmServer = new GenericContainer<>("wiremock/wiremock:3.3.1") 48 | .withClasspathResourceMapping("wiremock", "/home/wiremock", BindMode.READ_ONLY) 49 | .withExposedPorts(WIREMOCK_PORT); 50 | 51 | @Test 52 | void shouldCreateBeans() throws Exception { 53 | final String tempWalletName = WalletUtils.generateFullNewWalletFile("changeit", tempWalletDir); 54 | final String tempWalletPath = tempWalletDir.getAbsolutePath() + File.separator + tempWalletName; 55 | runner.withPropertyValues("worker.name=worker", "worker.worker-base-dir=/tmp", "worker.override-available-cpu-count=", 56 | "worker.gpu-enabled=false", "worker.gas-price-multiplier=1.0", "worker.gas-price-cap=22000000000", 57 | "worker.override-blockchain-node-address=", "worker.developer-logger-enabled=true", 58 | "worker.tee-compute-max-heap-size-gb=8", "worker.docker-network-name=iexec-worker-net") 59 | .withBean(ConfigServerConfigurationService.class) 60 | .withBean(IexecHubService.class) 61 | .withBean(PublicConfigurationService.class) 62 | .withBean(WalletConfiguration.class, tempWalletPath, "changeit") 63 | .withBean(Web3jService.class) 64 | .withBean(WorkerConfigurationService.class) 65 | .withBean(SchedulerConfiguration.class, "http://localhost:" + wmServer.getMappedPort(WIREMOCK_PORT), "0x365E7BABAa85eC61Dffe5b520763062e6C29dA27") 66 | .run(context -> assertThat(context) 67 | .hasSingleBean(ConfigServerClient.class) 68 | .hasSingleBean(ConfigServerConfigurationService.class) 69 | .hasSingleBean(IexecHubService.class) 70 | .hasSingleBean(PublicConfigurationService.class) 71 | .hasSingleBean(SchedulerConfiguration.class) 72 | .hasSingleBean(SignerService.class) 73 | .hasSingleBean(WalletConfiguration.class) 74 | .hasSingleBean(Web3jService.class) 75 | .hasSingleBean(WorkerConfigurationService.class)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/chain/Web3jServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import com.iexec.worker.config.ConfigServerConfigurationService; 20 | import com.iexec.worker.config.WorkerConfigurationService; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | import org.mockito.Mock; 24 | import org.mockito.MockitoAnnotations; 25 | 26 | import java.time.Duration; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.mockito.Mockito.when; 30 | 31 | class Web3jServiceTests { 32 | @Mock 33 | private ConfigServerConfigurationService configServerConfigurationService; 34 | @Mock 35 | private WorkerConfigurationService workerConfigurationService; 36 | 37 | @BeforeEach 38 | void init() { 39 | MockitoAnnotations.openMocks(this); 40 | when(configServerConfigurationService.getChainId()).thenReturn(134); 41 | when(configServerConfigurationService.getBlockTime()).thenReturn(Duration.ofSeconds(5)); 42 | when(configServerConfigurationService.isSidechain()).thenReturn(true); 43 | when(configServerConfigurationService.getChainNodeUrl()).thenReturn("https://bellecour.iex.ec"); 44 | when(workerConfigurationService.getGasPriceMultiplier()).thenReturn(1.0f); 45 | when(workerConfigurationService.getGasPriceCap()).thenReturn(22_000_000_000L); 46 | } 47 | 48 | @Test 49 | void shouldCreateInstanceWithDefaultNodeAddress() { 50 | when(workerConfigurationService.getOverrideBlockchainNodeAddress()).thenReturn(""); 51 | assertThat(new Web3jService(configServerConfigurationService, workerConfigurationService)).isNotNull(); 52 | } 53 | 54 | @Test 55 | void shouldCreateInstanceWithOverridenNodeAddress() { 56 | when(workerConfigurationService.getOverrideBlockchainNodeAddress()).thenReturn("http://localhost:8545"); 57 | assertThat(new Web3jService(configServerConfigurationService, workerConfigurationService)).isNotNull(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/chain/WebSocketBlockchainListenerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.chain; 18 | 19 | import com.iexec.worker.TestApplication; 20 | import io.micrometer.core.instrument.Gauge; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | import org.springframework.test.context.ActiveProfiles; 26 | import org.springframework.test.context.DynamicPropertyRegistry; 27 | import org.springframework.test.context.DynamicPropertySource; 28 | import org.testcontainers.containers.ComposeContainer; 29 | import org.testcontainers.containers.wait.strategy.Wait; 30 | import org.testcontainers.junit.jupiter.Container; 31 | import org.testcontainers.junit.jupiter.Testcontainers; 32 | 33 | import java.io.File; 34 | import java.util.Objects; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | import static com.iexec.worker.chain.WebSocketBlockchainListener.LATEST_BLOCK_METRIC_NAME; 38 | import static com.iexec.worker.chain.WebSocketBlockchainListener.TX_COUNT_METRIC_NAME; 39 | import static org.assertj.core.api.Assertions.assertThat; 40 | import static org.awaitility.Awaitility.await; 41 | 42 | @Testcontainers 43 | @SpringBootTest(classes = TestApplication.class) 44 | @ActiveProfiles("test") 45 | class WebSocketBlockchainListenerTests { 46 | 47 | private static final String CHAIN_SVC_NAME = "chain"; 48 | private static final int CHAIN_SVC_PORT = 8545; 49 | private static final String CORE_SVC_NAME = "core"; 50 | private static final int CORE_SVC_PORT = 8080; 51 | 52 | @Container 53 | static ComposeContainer environment = new ComposeContainer(new File("docker-compose.yml")) 54 | .withExposedService(CHAIN_SVC_NAME, CHAIN_SVC_PORT, Wait.forListeningPort()) 55 | .withExposedService(CORE_SVC_NAME, CORE_SVC_PORT, Wait.forListeningPort()) 56 | .withPull(true); 57 | 58 | @DynamicPropertySource 59 | static void registerProperties(DynamicPropertyRegistry registry) { 60 | final String coreHost = environment.getServiceHost(CORE_SVC_NAME, CORE_SVC_PORT); 61 | final int corePort = environment.getServicePort(CORE_SVC_NAME, CORE_SVC_PORT); 62 | 63 | registry.add("core.url", () -> getServiceUrl(coreHost, corePort)); 64 | registry.add("core.pool-address", () -> "0x1"); 65 | registry.add("worker.override-blockchain-node-address", () -> getServiceUrl( 66 | environment.getServiceHost(CHAIN_SVC_NAME, CHAIN_SVC_PORT), 67 | environment.getServicePort(CHAIN_SVC_NAME, CHAIN_SVC_PORT))); 68 | } 69 | 70 | @Autowired 71 | private MeterRegistry meterRegistry; 72 | 73 | @Autowired 74 | private Web3jService web3jService; 75 | 76 | private static String getServiceUrl(String serviceHost, int servicePort) { 77 | return "http://" + serviceHost + ":" + servicePort; 78 | } 79 | 80 | @Test 81 | void shouldConnect() { 82 | await().atMost(10L, TimeUnit.SECONDS) 83 | .until(() -> web3jService.getLatestBlockNumber() != 0); 84 | assertThat(meterRegistry.find(TX_COUNT_METRIC_NAME).tag("block", "latest").gauge()) 85 | .isNotNull() 86 | .extracting(Gauge::value) 87 | .isEqualTo(0.0); 88 | assertThat(meterRegistry.find(TX_COUNT_METRIC_NAME).tag("block", "pending").gauge()) 89 | .isNotNull() 90 | .extracting(Gauge::value) 91 | .isEqualTo(0.0); 92 | final Long latestBlockNumber = (long) Objects.requireNonNull(meterRegistry.find(LATEST_BLOCK_METRIC_NAME).gauge()).value(); 93 | assertThat(latestBlockNumber).isEqualTo(web3jService.getLatestBlockNumber()); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/compute/ComputeExitCauseServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.compute; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import org.assertj.core.api.Assertions; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | 24 | class ComputeExitCauseServiceTests { 25 | 26 | public static final String CHAIN_TASK_ID = "chainTaskId"; 27 | 28 | private ComputeExitCauseService computeExitCauseService; 29 | 30 | @BeforeEach 31 | void before() { 32 | computeExitCauseService = new ComputeExitCauseService(); 33 | } 34 | 35 | //region getPreComputeExitCauseAndPrune 36 | @Test 37 | void setAndGetPreComputeExitCauseAndPrune() { 38 | ReplicateStatusCause cause = 39 | ReplicateStatusCause.PRE_COMPUTE_DATASET_URL_MISSING; 40 | Assertions.assertThat(computeExitCauseService.setExitCause(ComputeStage.PRE, 41 | CHAIN_TASK_ID, 42 | cause)).isTrue(); 43 | Assertions.assertThat(computeExitCauseService.getPreComputeExitCauseAndPrune(CHAIN_TASK_ID)) 44 | .isEqualTo(cause); 45 | Assertions.assertThat(computeExitCauseService.getReplicateStatusCause(ComputeStage.PRE, 46 | CHAIN_TASK_ID)).isNull(); 47 | } 48 | 49 | @Test 50 | void shouldReturnUnknownIssueWhenPreComputeExitCauseNotSet() { 51 | ReplicateStatusCause cause = computeExitCauseService.getPreComputeExitCauseAndPrune(CHAIN_TASK_ID); 52 | Assertions.assertThat(cause) 53 | .isNotNull() 54 | .isEqualTo(ReplicateStatusCause.PRE_COMPUTE_FAILED_UNKNOWN_ISSUE); 55 | } 56 | //endregion 57 | 58 | //region getPostComputeExitCauseAndPrune 59 | @Test 60 | void setAndGetPostComputeExitCauseAndPrune() { 61 | ReplicateStatusCause cause = 62 | ReplicateStatusCause.POST_COMPUTE_COMPUTED_FILE_NOT_FOUND; 63 | Assertions.assertThat(computeExitCauseService.setExitCause(ComputeStage.POST, 64 | CHAIN_TASK_ID, 65 | cause)).isTrue(); 66 | Assertions.assertThat(computeExitCauseService.getPostComputeExitCauseAndPrune(CHAIN_TASK_ID)) 67 | .isEqualTo(cause); 68 | Assertions.assertThat(computeExitCauseService.getReplicateStatusCause(ComputeStage.POST, 69 | CHAIN_TASK_ID)).isNull(); 70 | } 71 | 72 | @Test 73 | void shouldReturnUnknownIssueWhenPostComputeExitCauseNotSet() { 74 | ReplicateStatusCause cause = computeExitCauseService.getPostComputeExitCauseAndPrune(CHAIN_TASK_ID); 75 | Assertions.assertThat(cause) 76 | .isNotNull() 77 | .isEqualTo(ReplicateStatusCause.POST_COMPUTE_FAILED_UNKNOWN_ISSUE); 78 | } 79 | //endregion 80 | 81 | @Test 82 | void shouldNotSetComputeExitCauseSinceAlreadySet() { 83 | ReplicateStatusCause cause = 84 | ReplicateStatusCause.PRE_COMPUTE_DATASET_URL_MISSING; 85 | computeExitCauseService.setExitCause(ComputeStage.POST, 86 | CHAIN_TASK_ID, 87 | cause); 88 | Assertions.assertThat(computeExitCauseService.setExitCause(ComputeStage.POST, 89 | CHAIN_TASK_ID, 90 | cause)).isFalse(); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/compute/ComputeStageConverterTests.java: -------------------------------------------------------------------------------- 1 | package com.iexec.worker.compute; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.Arguments; 5 | import org.junit.jupiter.params.provider.MethodSource; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | 8 | import java.util.stream.Stream; 9 | 10 | import static com.iexec.worker.compute.ComputeStage.POST; 11 | import static com.iexec.worker.compute.ComputeStage.PRE; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | 15 | class ComputeStageConverterTests { 16 | private final ComputeStageConverter computeStageConverter = new ComputeStageConverter(); 17 | 18 | static Stream correctConversions() { 19 | return Stream.of( 20 | Arguments.of("PRE", PRE), 21 | Arguments.of("Pre", PRE), 22 | Arguments.of("pre", PRE), 23 | Arguments.of("POST", POST), 24 | Arguments.of("Post", POST), 25 | Arguments.of("post", POST) 26 | ); 27 | } 28 | 29 | @ParameterizedTest 30 | @MethodSource("correctConversions") 31 | void shouldConvert(String name, ComputeStage expectedValue) { 32 | assertEquals(expectedValue, computeStageConverter.convert(name)); 33 | } 34 | 35 | @ParameterizedTest 36 | @ValueSource(strings = {"", "app"}) 37 | void shouldRejectConversion(String name) { 38 | assertThrows(IllegalArgumentException.class, () -> computeStageConverter.convert(name)); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/config/ConfigServerConfigurationServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import com.iexec.common.config.ConfigServerClient; 20 | import com.iexec.common.config.PublicChainConfig; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | import org.mockito.Mock; 24 | import org.mockito.MockitoAnnotations; 25 | 26 | import java.time.Duration; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.junit.jupiter.api.Assertions.assertThrows; 30 | import static org.mockito.Mockito.when; 31 | 32 | class ConfigServerConfigurationServiceTests { 33 | private static final Integer CHAIN_ID = 0; 34 | private static final boolean IS_SIDECHAIN = true; 35 | private static final String NODE_ADDRESS = "https://node"; 36 | private static final String HUB_ADDRESS = "0x2"; 37 | private static final Duration BLOCK_TIME = Duration.ofSeconds(1); 38 | 39 | @Mock 40 | private ConfigServerClient configServerClient; 41 | 42 | @BeforeEach 43 | void beforeEach() { 44 | MockitoAnnotations.openMocks(this); 45 | } 46 | 47 | @Test 48 | void shouldGetBlockTime() { 49 | when(configServerClient.getPublicChainConfig()).thenReturn( 50 | PublicChainConfig.builder() 51 | .chainId(CHAIN_ID) 52 | .sidechain(IS_SIDECHAIN) 53 | .chainNodeUrl(NODE_ADDRESS) 54 | .iexecHubContractAddress(HUB_ADDRESS) 55 | .blockTime(BLOCK_TIME) 56 | .build() 57 | ); 58 | 59 | ConfigServerConfigurationService configServerConfigurationService = 60 | new ConfigServerConfigurationService(configServerClient); 61 | 62 | assertThat(configServerConfigurationService.getChainId()) 63 | .isEqualTo(CHAIN_ID); 64 | assertThat(configServerConfigurationService.isSidechain()) 65 | .isEqualTo(IS_SIDECHAIN); 66 | assertThat(configServerConfigurationService.getChainNodeUrl()) 67 | .isEqualTo(NODE_ADDRESS); 68 | assertThat(configServerConfigurationService.getIexecHubContractAddress()) 69 | .isEqualTo(HUB_ADDRESS); 70 | assertThat(configServerConfigurationService.getBlockTime()) 71 | .isEqualTo(BLOCK_TIME); 72 | } 73 | 74 | @Test() 75 | void shouldRaiseMissingConfigurationExceptionWhenPublicChainConfigIsNull() { 76 | when(configServerClient.getPublicChainConfig()).thenReturn(null); 77 | assertThrows(MissingConfigurationException.class, () -> { 78 | new ConfigServerConfigurationService(configServerClient); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/config/PublicConfigurationServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import com.iexec.core.api.SchedulerClient; 20 | import com.iexec.core.config.PublicConfiguration; 21 | import com.iexec.resultproxy.api.ResultProxyClientBuilder; 22 | import feign.Logger; 23 | import org.junit.jupiter.api.Test; 24 | import org.junit.jupiter.api.extension.ExtendWith; 25 | import org.mockito.Mock; 26 | import org.mockito.junit.jupiter.MockitoExtension; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.junit.jupiter.api.Assertions.assertAll; 30 | import static org.mockito.Mockito.when; 31 | 32 | @ExtendWith(MockitoExtension.class) 33 | class PublicConfigurationServiceTests { 34 | 35 | @Mock 36 | private SchedulerClient schedulerClient; 37 | 38 | @Test 39 | void shouldBeOK() { 40 | when(schedulerClient.getPublicConfiguration()).thenReturn( 41 | PublicConfiguration.builder() 42 | .configServerUrl("http://localhost:8888") 43 | .resultRepositoryURL("http://localhost:13300") 44 | .requiredWorkerVersion("v8") 45 | .schedulerPublicAddress(("http://localhost:1300")) 46 | .build() 47 | ); 48 | 49 | final PublicConfigurationService publicConfigurationService = new PublicConfigurationService(schedulerClient); 50 | 51 | assertAll( 52 | () -> assertThat(publicConfigurationService).isNotNull(), 53 | () -> assertThat(publicConfigurationService.getSchedulerPublicAddress()).isEqualTo("http://localhost:1300"), 54 | () -> assertThat(publicConfigurationService.getRequiredWorkerVersion()).isEqualTo("v8"), 55 | () -> assertThat(publicConfigurationService.configServerClient()).isNotNull(), 56 | () -> assertThat(publicConfigurationService.createResultProxyClientFromURL(null)).isNotNull(), 57 | () -> assertThat(publicConfigurationService.createResultProxyClientFromURL("")) 58 | .isEqualTo(ResultProxyClientBuilder.getInstance(Logger.Level.NONE, "http://localhost:13300")), 59 | () -> assertThat(publicConfigurationService.createResultProxyClientFromURL("https://www.result-proxy-repo.iex.ec")) 60 | .isEqualTo(ResultProxyClientBuilder.getInstance(Logger.Level.NONE, "https://www.result-proxy-repo.iex.ec")) 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/config/PurgeConfigurationTests.java: -------------------------------------------------------------------------------- 1 | package com.iexec.worker.config; 2 | 3 | import com.iexec.common.lifecycle.purge.PurgeService; 4 | import com.iexec.common.lifecycle.purge.Purgeable; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.List; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | import static org.mockito.Mockito.mock; 11 | 12 | class PurgeConfigurationTests { 13 | final PurgeConfiguration purgeConfiguration = new PurgeConfiguration(); 14 | 15 | @Test 16 | void createPurgeService() { 17 | final List purgeables = List.of( 18 | mock(Purgeable.class), 19 | mock(Purgeable.class), 20 | mock(Purgeable.class) 21 | ); 22 | final PurgeService purgeService = purgeConfiguration.purgeService(purgeables); 23 | assertNotNull(purgeService); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/config/SchedulerConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import com.iexec.core.api.SchedulerClient; 20 | import org.junit.jupiter.api.Test; 21 | import org.junit.jupiter.params.ParameterizedTest; 22 | import org.junit.jupiter.params.provider.ValueSource; 23 | import org.springframework.beans.factory.BeanCreationException; 24 | import org.springframework.boot.test.context.runner.ApplicationContextRunner; 25 | 26 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; 27 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; 28 | 29 | class SchedulerConfigurationTests { 30 | private final ApplicationContextRunner runner = new ApplicationContextRunner(); 31 | 32 | @Test 33 | void shouldCreateBeanInstance() { 34 | runner.withBean(SchedulerConfiguration.class, "http://localhost:13000", "0x365E7BABAa85eC61Dffe5b520763062e6C29dA27") 35 | .run(context -> { 36 | assertThat(context).hasSingleBean(SchedulerClient.class); 37 | assertThat(context).getBean("schedulerConfiguration", SchedulerConfiguration.class) 38 | .extracting("poolAddress") 39 | .isEqualTo("0x365E7BABAa85eC61Dffe5b520763062e6C29dA27"); 40 | }); 41 | } 42 | 43 | @ParameterizedTest 44 | @ValueSource(strings = {"", "0x0"}) 45 | void shouldFailedAndRaisedExceptionWhenPoolAddressIsInvalid(String poolAddress) { 46 | runner.withBean(SchedulerConfiguration.class, "http://localhost:13000", poolAddress) 47 | .run(context -> { 48 | assertThatThrownBy(() -> context.getBean(SchedulerConfiguration.class)) 49 | .isInstanceOf(IllegalStateException.class) 50 | .hasCauseInstanceOf(BeanCreationException.class) 51 | .hasRootCauseMessage("The workerpool address must be filled in"); 52 | }); 53 | } 54 | 55 | @Test 56 | void shouldFailWhenUrlIsEmpty() { 57 | runner.withBean(SchedulerConfiguration.class, "", "0x365E7BABAa85eC61Dffe5b520763062e6C29dA27") 58 | .run(context -> { 59 | assertThat(context).hasFailed(); 60 | assertThat(context.getStartupFailure()) 61 | .isInstanceOf(BeanCreationException.class) 62 | .hasMessageContaining("Error creating bean with name 'schedulerClient'"); 63 | }); 64 | } 65 | 66 | @Test 67 | void shouldPassWithValidUrl() { 68 | runner.withBean(SchedulerConfiguration.class, "http://localhost:8080", "0x365E7BABAa85eC61Dffe5b520763062e6C29dA27") 69 | .run(context -> { 70 | SchedulerConfiguration config = context.getBean(SchedulerConfiguration.class); 71 | assertThat(config.getUrl()).isEqualTo("http://localhost:8080"); 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/config/StompClientConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.Test; 21 | import org.mockito.Mock; 22 | import org.mockito.MockitoAnnotations; 23 | import org.springframework.messaging.converter.MappingJackson2MessageConverter; 24 | import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; 25 | import org.springframework.web.client.RestTemplate; 26 | import org.springframework.web.socket.messaging.WebSocketStompClient; 27 | import org.springframework.web.socket.sockjs.client.SockJsClient; 28 | 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import static org.junit.jupiter.api.Assertions.*; 31 | 32 | class StompClientConfigurationTests { 33 | @Mock 34 | RestTemplate restTemplate; 35 | 36 | StompClientConfiguration stompClientConfiguration = new StompClientConfiguration(); 37 | 38 | @BeforeEach 39 | void init() { 40 | MockitoAnnotations.openMocks(this); 41 | } 42 | 43 | @Test 44 | void shouldCreateStompClient() { 45 | final WebSocketStompClient stompClient = stompClientConfiguration.stompClient(restTemplate); 46 | assertAll( 47 | () -> assertThat(stompClient).isNotNull(), 48 | () -> assertThat(stompClient).extracting(WebSocketStompClient::isAutoStartup).isEqualTo(true), 49 | () -> assertThat(stompClient).extracting(WebSocketStompClient::getMessageConverter).isInstanceOf(MappingJackson2MessageConverter.class), 50 | () -> assertThat(stompClient).extracting(WebSocketStompClient::getTaskScheduler).isInstanceOf(ConcurrentTaskScheduler.class), 51 | () -> assertThat(stompClient).extracting(WebSocketStompClient::getWebSocketClient).isInstanceOf(SockJsClient.class) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/config/WorkerConfigurationServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.config; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static java.lang.management.ManagementFactory.getOperatingSystemMXBean; 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | class WorkerConfigurationServiceTests { 25 | private final WorkerConfigurationService workerConfiguration = new WorkerConfigurationService(); 26 | 27 | @Test 28 | void shouldGetDefaultCpuCount() { 29 | final int defaultAvailableCpuCount = Math.max(Runtime.getRuntime().availableProcessors() - 1, 1); 30 | assertThat(workerConfiguration.getCpuCount()).isEqualTo(defaultAvailableCpuCount); 31 | } 32 | 33 | @Test 34 | void shouldGetMemorySize() { 35 | final com.sun.management.OperatingSystemMXBean os = (com.sun.management.OperatingSystemMXBean) getOperatingSystemMXBean(); 36 | assertThat(workerConfiguration.getMemorySize()).isEqualTo((int) os.getTotalMemorySize() / (1024 * 1024 * 1024)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/docker/DockerRegistryConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.docker; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import java.util.List; 22 | import java.util.Optional; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.junit.jupiter.api.Assertions.assertThrows; 26 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 27 | 28 | class DockerRegistryConfigurationTests { 29 | 30 | // Get a valid instance of the class under test 31 | DockerRegistryConfiguration getValidConfiguration() { 32 | RegistryCredentials credentials = RegistryCredentials.builder() 33 | .address("address1") 34 | .username("username1") 35 | .password("password1") 36 | .build(); 37 | DockerRegistryConfiguration configuration = new DockerRegistryConfiguration(); 38 | configuration.setRegistries(List.of(credentials)); 39 | return configuration; 40 | } 41 | 42 | @Test 43 | void shouldNotThrowWhenRegistryListIsEmpty() { 44 | DockerRegistryConfiguration configuration1 = getValidConfiguration(); 45 | configuration1.setRegistries(null); 46 | assertDoesNotThrow(configuration1::validateRegistries); 47 | 48 | DockerRegistryConfiguration configuration2 = getValidConfiguration(); 49 | configuration2.setRegistries(List.of()); 50 | assertDoesNotThrow(configuration2::validateRegistries); 51 | } 52 | 53 | @Test 54 | void shouldNotThrowWhenRegistryListIsValid() { 55 | DockerRegistryConfiguration configuration = getValidConfiguration(); 56 | assertDoesNotThrow(configuration::validateRegistries); 57 | } 58 | 59 | @Test 60 | void shouldThrowWhenRegistryIsMissingPassword() { 61 | DockerRegistryConfiguration configuration = getValidConfiguration(); 62 | configuration.getRegistries().get(0).setPassword(null); 63 | assertThrows(IllegalArgumentException.class, configuration::validateRegistries); 64 | } 65 | 66 | // getRegistryCredentials 67 | 68 | @Test 69 | void shouldGetAuthForRegistry() { 70 | DockerRegistryConfiguration configuration = getValidConfiguration(); 71 | RegistryCredentials credentials = configuration.getRegistries().get(0); 72 | 73 | Optional authForRegistry = 74 | configuration.getRegistryCredentials(credentials.getAddress()); 75 | assertThat(authForRegistry).isPresent(); 76 | assertThat(authForRegistry.get().getAddress()).isEqualTo(credentials.getAddress()); 77 | assertThat(authForRegistry.get().getUsername()).isEqualTo(credentials.getUsername()); 78 | assertThat(authForRegistry.get().getPassword()).isEqualTo(credentials.getPassword()); 79 | } 80 | 81 | @Test 82 | void shouldNotGetAuthForRegistrySinceNoRegistries() { 83 | DockerRegistryConfiguration configuration = getValidConfiguration(); 84 | configuration.setRegistries(null); // no list 85 | assertThat(configuration.getRegistryCredentials("whatever")).isEmpty(); 86 | } 87 | 88 | @Test 89 | void shouldNotGetAuthForRegistrySinceUnknownRegistry() { 90 | DockerRegistryConfiguration configuration = getValidConfiguration(); 91 | assertThat(configuration.getRegistryCredentials("unknownRegistry")).isEmpty(); 92 | } 93 | 94 | @Test 95 | void shouldNotGetAuthForRegistrySinceMissingUsernameInConfig() { 96 | DockerRegistryConfiguration configuration = getValidConfiguration(); 97 | RegistryCredentials credentials = configuration.getRegistries().get(0); 98 | credentials.setUsername(null); // no username 99 | assertThat(configuration.getRegistryCredentials(credentials.getAddress())).isEmpty(); 100 | } 101 | 102 | @Test 103 | void shouldNotGetAuthForRegistrySinceMissingPasswordInConfig() { 104 | DockerRegistryConfiguration configuration = getValidConfiguration(); 105 | RegistryCredentials credentials = configuration.getRegistries().get(0); 106 | credentials.setPassword(null); // no password 107 | assertThat(configuration.getRegistryCredentials(credentials.getAddress())).isEmpty(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/feign/RestTemplateConfigTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.feign; 18 | 19 | import com.iexec.worker.config.WorkerConfigurationService; 20 | import com.iexec.worker.feign.config.RestTemplateConfig; 21 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | import org.mockito.InjectMocks; 25 | import org.mockito.Mock; 26 | import org.mockito.junit.jupiter.MockitoExtension; 27 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 28 | import org.springframework.web.client.RestTemplate; 29 | 30 | import static org.junit.jupiter.api.Assertions.assertNotNull; 31 | import static org.mockito.Mockito.when; 32 | 33 | @ExtendWith(MockitoExtension.class) 34 | class RestTemplateConfigTests { 35 | 36 | @Mock 37 | private WorkerConfigurationService workerConfService; 38 | 39 | @InjectMocks 40 | private RestTemplateConfig restTemplateConfig; 41 | 42 | @Test 43 | void testRestTemplateWithHttpsProxy() { 44 | when(workerConfService.getHttpsProxyHost()).thenReturn("https-proxy.example.com"); 45 | when(workerConfService.getHttpsProxyPort()).thenReturn(443); 46 | when(workerConfService.getHttpProxyHost()).thenReturn(null); 47 | when(workerConfService.getHttpProxyPort()).thenReturn(null); 48 | 49 | final RestTemplate restTemplate = restTemplateConfig.restTemplate(); 50 | 51 | assertNotNull(restTemplate); 52 | final HttpComponentsClientHttpRequestFactory factory = 53 | (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory(); 54 | assertNotNull(factory); 55 | final CloseableHttpClient httpClient = (CloseableHttpClient) factory.getHttpClient(); 56 | assertNotNull(httpClient); 57 | } 58 | 59 | @Test 60 | void testRestTemplateWithHttpProxy() { 61 | when(workerConfService.getHttpsProxyHost()).thenReturn(null); 62 | when(workerConfService.getHttpsProxyPort()).thenReturn(null); 63 | when(workerConfService.getHttpProxyHost()).thenReturn("http-proxy.example.com"); 64 | when(workerConfService.getHttpProxyPort()).thenReturn(8080); 65 | 66 | final RestTemplate restTemplate = restTemplateConfig.restTemplate(); 67 | 68 | assertNotNull(restTemplate); 69 | final HttpComponentsClientHttpRequestFactory factory = 70 | (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory(); 71 | assertNotNull(factory); 72 | final CloseableHttpClient httpClient = (CloseableHttpClient) factory.getHttpClient(); 73 | assertNotNull(httpClient); 74 | } 75 | 76 | @Test 77 | void testRestTemplateNoProxy() { 78 | when(workerConfService.getHttpsProxyHost()).thenReturn(null); 79 | when(workerConfService.getHttpsProxyPort()).thenReturn(null); 80 | when(workerConfService.getHttpProxyHost()).thenReturn(null); 81 | when(workerConfService.getHttpProxyPort()).thenReturn(null); 82 | 83 | final RestTemplate restTemplate = restTemplateConfig.restTemplate(); 84 | 85 | assertNotNull(restTemplate); 86 | final HttpComponentsClientHttpRequestFactory factory = 87 | (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory(); 88 | assertNotNull(factory); 89 | final CloseableHttpClient httpClient = (CloseableHttpClient) factory.getHttpClient(); 90 | assertNotNull(httpClient); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/metric/ComputeDurationsConfigTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.api.Test; 22 | import org.springframework.test.util.ReflectionTestUtils; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | class ComputeDurationsConfigTests { 27 | private static final int WINDOW_SIZE = 1_000; 28 | private static final String WALLET_ADDRESS = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; 29 | 30 | private ComputeDurationsConfig computeDurationsConfig; 31 | 32 | @BeforeEach 33 | void init() { 34 | computeDurationsConfig = new ComputeDurationsConfig(WINDOW_SIZE); 35 | } 36 | 37 | // region Constructor 38 | @Test 39 | void shouldConstructConfigWithWindowSize() { 40 | final Object windowSize = ReflectionTestUtils.getField(computeDurationsConfig, "windowSize"); 41 | assertThat(windowSize).isEqualTo(WINDOW_SIZE); 42 | } 43 | // endregion 44 | 45 | // region preComputeDurationService 46 | @Test 47 | void shouldConstructPreComputeDurationService() { 48 | final ComputeDurationsService preComputeDurationsService = 49 | computeDurationsConfig.preComputeDurationsService(new SimpleMeterRegistry(), WALLET_ADDRESS); 50 | assertThat(preComputeDurationsService).isNotNull(); 51 | } 52 | // endregion 53 | 54 | // region appComputeDurationService 55 | @Test 56 | void shouldConstructAppComputeDurationService() { 57 | final ComputeDurationsService appComputeDurationsService = 58 | computeDurationsConfig.appComputeDurationsService(new SimpleMeterRegistry(), WALLET_ADDRESS); 59 | assertThat(appComputeDurationsService).isNotNull(); 60 | } 61 | // endregion 62 | 63 | // region postComputeDurationService 64 | @Test 65 | void shouldConstructPostComputeDurationService() { 66 | final ComputeDurationsService postComputeDurationsService = 67 | computeDurationsConfig.postComputeDurationsService(new SimpleMeterRegistry(), WALLET_ADDRESS); 68 | assertThat(postComputeDurationsService).isNotNull(); 69 | } 70 | // endregion 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/metric/MetricsControllerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.metric; 18 | 19 | import org.assertj.core.api.Assertions; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.api.Test; 22 | import org.mockito.InjectMocks; 23 | import org.mockito.Mock; 24 | import org.mockito.MockitoAnnotations; 25 | import org.springframework.http.HttpStatus; 26 | import org.springframework.http.ResponseEntity; 27 | 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.when; 30 | 31 | class MetricsControllerTests { 32 | @Mock 33 | private MetricsService metricsService; 34 | 35 | @InjectMocks 36 | private MetricsController metricsController; 37 | 38 | @BeforeEach 39 | void init() { 40 | MockitoAnnotations.openMocks(this); 41 | } 42 | 43 | // region getWorkerMetrics 44 | @Test 45 | void shouldGetWorkerMetrics() { 46 | final WorkerMetrics metrics = mock(WorkerMetrics.class); 47 | when(metricsService.getWorkerMetrics()).thenReturn(metrics); 48 | 49 | final ResponseEntity response = metricsController.getWorkerMetrics(); 50 | 51 | Assertions.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 52 | Assertions.assertThat(response.getBody()).isEqualTo(metrics); 53 | } 54 | // endregion 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/sgx/SgxServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.sgx; 18 | 19 | import com.github.dockerjava.api.model.Device; 20 | import com.iexec.commons.containers.SgxDriverMode; 21 | import com.iexec.worker.docker.DockerService; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | import org.junit.jupiter.params.ParameterizedTest; 25 | import org.junit.jupiter.params.provider.Arguments; 26 | import org.junit.jupiter.params.provider.MethodSource; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.boot.test.system.CapturedOutput; 29 | import org.springframework.boot.test.system.OutputCaptureExtension; 30 | import org.springframework.context.ApplicationContext; 31 | 32 | import java.util.List; 33 | import java.util.stream.Stream; 34 | 35 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 36 | import static org.junit.jupiter.api.Assertions.assertAll; 37 | 38 | @ExtendWith(OutputCaptureExtension.class) 39 | class SgxServiceTests { 40 | private static final String WORKER_WALLET_ADDRESS = "0x2D29bfBEc903479fe4Ba991918bAB99B494f2bEf"; 41 | 42 | @Autowired 43 | private ApplicationContext context; 44 | @Autowired 45 | private DockerService dockerService; 46 | 47 | @Test 48 | void modeNone(CapturedOutput output) { 49 | SgxService sgxService = new SgxService(context, dockerService, SgxDriverMode.NONE, WORKER_WALLET_ADDRESS); 50 | sgxService.init(); 51 | assertAll( 52 | () -> assertThat(sgxService.isSgxEnabled()).isFalse(), 53 | () -> assertThat(output.getAll()).contains("No SGX driver defined, skipping SGX check [sgxDriverMode:NONE]") 54 | ); 55 | } 56 | 57 | // region getSgxDevices 58 | @ParameterizedTest 59 | @MethodSource("sgxDevicesCheckParams") 60 | void checkGetSgxDevices(SgxDriverMode driverMode, List expectedDevices) { 61 | SgxService sgxService = new SgxService(context, dockerService, driverMode, WORKER_WALLET_ADDRESS); 62 | assertThat(sgxService.getSgxDevices()).isEqualTo(expectedDevices); 63 | } 64 | 65 | static Stream sgxDevicesCheckParams() { 66 | return Stream.of( 67 | Arguments.of(SgxDriverMode.NONE, List.of()) 68 | ); 69 | } 70 | // endregion 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/tee/TeeServiceMock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee; 18 | 19 | import com.iexec.commons.poco.task.TaskDescription; 20 | import com.iexec.sms.api.TeeSessionGenerationResponse; 21 | import com.iexec.worker.sgx.SgxService; 22 | import com.iexec.worker.sms.SmsService; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | class TeeServiceMock extends TeeService { 29 | 30 | protected TeeServiceMock(SgxService sgxService, 31 | SmsService smsService, 32 | TeeServicesPropertiesService teeServicesPropertiesService) { 33 | super(sgxService, smsService, teeServicesPropertiesService); 34 | } 35 | 36 | @Override 37 | public boolean prepareTeeForTask(String chainTaskId) { 38 | return false; 39 | } 40 | 41 | @Override 42 | public List buildPreComputeDockerEnv(TaskDescription taskDescription, @NotNull TeeSessionGenerationResponse session) { 43 | return null; 44 | } 45 | 46 | @Override 47 | public List buildComputeDockerEnv(TaskDescription taskDescription, @NotNull TeeSessionGenerationResponse session) { 48 | return null; 49 | } 50 | 51 | @Override 52 | public List buildPostComputeDockerEnv(TaskDescription taskDescription, @NotNull TeeSessionGenerationResponse session) { 53 | return null; 54 | } 55 | 56 | @Override 57 | public Collection getAdditionalBindings() { 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/tee/TeeServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee; 18 | 19 | import com.iexec.common.replicate.ReplicateStatusCause; 20 | import com.iexec.sms.api.SmsClient; 21 | import com.iexec.sms.api.SmsClientCreationException; 22 | import com.iexec.worker.sgx.SgxService; 23 | import com.iexec.worker.sms.SmsService; 24 | import org.junit.jupiter.api.Assertions; 25 | import org.junit.jupiter.api.Test; 26 | import org.junit.jupiter.api.extension.ExtendWith; 27 | import org.mockito.InjectMocks; 28 | import org.mockito.Mock; 29 | import org.mockito.Spy; 30 | import org.mockito.junit.jupiter.MockitoExtension; 31 | 32 | import java.util.Optional; 33 | 34 | import static com.iexec.common.replicate.ReplicateStatusCause.*; 35 | import static org.junit.jupiter.api.Assertions.assertFalse; 36 | import static org.junit.jupiter.api.Assertions.assertTrue; 37 | import static org.mockito.Mockito.verify; 38 | import static org.mockito.Mockito.when; 39 | 40 | @ExtendWith(MockitoExtension.class) 41 | class TeeServiceTests { 42 | private static final String CHAIN_TASK_ID = "CHAIN_TASK_ID"; 43 | 44 | @Mock 45 | SgxService sgxService; 46 | @Mock 47 | SmsService smsService; 48 | @Mock 49 | SmsClient smsClient; 50 | @Mock 51 | TeeServicesPropertiesService teeServicesPropertiesService; 52 | 53 | @Spy 54 | @InjectMocks 55 | TeeServiceMock teeService; 56 | 57 | // region isTeeEnabled 58 | @Test 59 | void shouldTeeBeEnabled() { 60 | when(sgxService.isSgxEnabled()).thenReturn(true); 61 | 62 | assertTrue(teeService.isTeeEnabled()); 63 | 64 | verify(sgxService).isSgxEnabled(); 65 | } 66 | 67 | @Test 68 | void shouldTeeNotBeEnabled() { 69 | when(sgxService.isSgxEnabled()).thenReturn(false); 70 | 71 | assertFalse(teeService.isTeeEnabled()); 72 | 73 | verify(sgxService).isSgxEnabled(); 74 | } 75 | // endregion 76 | 77 | // region areTeePrerequisitesMetForTask 78 | @Test 79 | void shouldTeePrerequisitesBeMet() { 80 | when(teeService.isTeeEnabled()).thenReturn(true); 81 | when(smsService.getSmsClient(CHAIN_TASK_ID)).thenReturn(smsClient); 82 | when(teeServicesPropertiesService.getTeeServicesProperties(CHAIN_TASK_ID)).thenReturn(null); 83 | 84 | Optional teePrerequisitesIssue = teeService.areTeePrerequisitesMetForTask(CHAIN_TASK_ID); 85 | 86 | Assertions.assertTrue(teePrerequisitesIssue.isEmpty()); 87 | } 88 | 89 | @Test 90 | void shouldTeePrerequisitesNotBeMetSinceTeeNotEnabled() { 91 | when(teeService.isTeeEnabled()).thenReturn(false); 92 | 93 | Optional teePrerequisitesIssue = teeService.areTeePrerequisitesMetForTask(CHAIN_TASK_ID); 94 | 95 | Assertions.assertTrue(teePrerequisitesIssue.isPresent()); 96 | Assertions.assertEquals(TEE_NOT_SUPPORTED, teePrerequisitesIssue.get()); 97 | } 98 | 99 | @Test 100 | void shouldTeePrerequisitesNotBeMetSinceSmsClientCantBeLoaded() { 101 | when(teeService.isTeeEnabled()).thenReturn(true); 102 | when(smsService.getSmsClient(CHAIN_TASK_ID)).thenThrow(SmsClientCreationException.class); 103 | 104 | Optional teePrerequisitesIssue = teeService.areTeePrerequisitesMetForTask(CHAIN_TASK_ID); 105 | 106 | Assertions.assertTrue(teePrerequisitesIssue.isPresent()); 107 | Assertions.assertEquals(UNKNOWN_SMS, teePrerequisitesIssue.get()); 108 | } 109 | 110 | @Test 111 | void shouldTeePrerequisitesNotBeMetSinceTeeEnclaveConfigurationIsNull() { 112 | when(teeService.isTeeEnabled()).thenReturn(true); 113 | when(smsService.getSmsClient(CHAIN_TASK_ID)).thenReturn(smsClient); 114 | when(teeServicesPropertiesService.getTeeServicesProperties(CHAIN_TASK_ID)).thenThrow(NullPointerException.class); 115 | 116 | Optional teePrerequisitesIssue = teeService.areTeePrerequisitesMetForTask(CHAIN_TASK_ID); 117 | 118 | Assertions.assertTrue(teePrerequisitesIssue.isPresent()); 119 | Assertions.assertEquals(PRE_COMPUTE_MISSING_ENCLAVE_CONFIGURATION, teePrerequisitesIssue.get()); 120 | } 121 | 122 | @Test 123 | void shouldTeePrerequisitesNotBeMetSinceTeeWorkflowConfigurationCantBeLoaded() { 124 | when(teeService.isTeeEnabled()).thenReturn(true); 125 | when(smsService.getSmsClient(CHAIN_TASK_ID)).thenReturn(smsClient); 126 | when(teeServicesPropertiesService.getTeeServicesProperties(CHAIN_TASK_ID)).thenThrow(RuntimeException.class); 127 | 128 | Optional teePrerequisitesIssue = teeService.areTeePrerequisitesMetForTask(CHAIN_TASK_ID); 129 | 130 | Assertions.assertTrue(teePrerequisitesIssue.isPresent()); 131 | Assertions.assertEquals(GET_TEE_SERVICES_CONFIGURATION_FAILED, teePrerequisitesIssue.get()); 132 | } 133 | // endregion 134 | } -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/tee/TeeServicesManagerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee; 18 | 19 | import com.iexec.commons.poco.tee.TeeFramework; 20 | import com.iexec.worker.tee.gramine.TeeGramineService; 21 | import com.iexec.worker.tee.scone.TeeSconeService; 22 | import org.junit.jupiter.api.BeforeEach; 23 | import org.junit.jupiter.api.Test; 24 | import org.junit.jupiter.params.ParameterizedTest; 25 | import org.junit.jupiter.params.provider.Arguments; 26 | import org.junit.jupiter.params.provider.MethodSource; 27 | import org.mockito.InjectMocks; 28 | import org.mockito.Mock; 29 | import org.mockito.MockitoAnnotations; 30 | 31 | import java.util.stream.Stream; 32 | 33 | import static org.junit.jupiter.api.Assertions.*; 34 | 35 | class TeeServicesManagerTests { 36 | 37 | @Mock 38 | TeeSconeService teeSconeService; 39 | @Mock 40 | TeeGramineService teeGramineService; 41 | 42 | @InjectMocks 43 | TeeServicesManager teeServicesManager; 44 | 45 | @BeforeEach 46 | void init() { 47 | MockitoAnnotations.openMocks(this); 48 | } 49 | 50 | static Stream teeServices() { 51 | return Stream.of( 52 | Arguments.of(TeeFramework.SCONE, TeeSconeService.class), 53 | Arguments.of(TeeFramework.GRAMINE, TeeGramineService.class) 54 | ); 55 | } 56 | 57 | @ParameterizedTest 58 | @MethodSource("teeServices") 59 | void shouldReturnTeeService(TeeFramework framework, Class teeService) { 60 | assertInstanceOf(teeService, teeServicesManager.getTeeService(framework)); 61 | } 62 | 63 | @Test 64 | void shouldThrowSinceNullProvider() { 65 | assertThrows(IllegalArgumentException.class, () -> teeServicesManager.getTeeService(null)); 66 | } 67 | } -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/tee/gramine/TeeGramineServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.tee.gramine; 18 | 19 | import com.iexec.commons.poco.task.TaskDescription; 20 | import com.iexec.sms.api.SmsClientProvider; 21 | import com.iexec.sms.api.TeeSessionGenerationResponse; 22 | import com.iexec.worker.sgx.SgxService; 23 | import com.iexec.worker.tee.TeeServicesPropertiesService; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.Test; 26 | import org.junit.jupiter.params.ParameterizedTest; 27 | import org.junit.jupiter.params.provider.NullSource; 28 | import org.junit.jupiter.params.provider.ValueSource; 29 | import org.mockito.InjectMocks; 30 | import org.mockito.Mock; 31 | import org.mockito.MockitoAnnotations; 32 | 33 | import java.util.Collection; 34 | import java.util.List; 35 | 36 | import static org.junit.jupiter.api.Assertions.*; 37 | import static org.mockito.Mockito.*; 38 | 39 | class TeeGramineServiceTests { 40 | private static final String SESSION_ID = "0x123_session_id"; 41 | private static final String SPS_URL = "http://spsUrl"; 42 | private static final TeeSessionGenerationResponse TEE_SESSION_GENERATION_RESPONSE = new TeeSessionGenerationResponse( 43 | SESSION_ID, 44 | SPS_URL 45 | ); 46 | 47 | @Mock 48 | SgxService sgxService; 49 | @Mock 50 | SmsClientProvider smsClientProvider; 51 | @Mock 52 | TeeServicesPropertiesService teeServicesPropertiesService; 53 | 54 | @InjectMocks 55 | TeeGramineService teeGramineService; 56 | 57 | @BeforeEach 58 | void init() { 59 | MockitoAnnotations.openMocks(this); 60 | } 61 | 62 | // region prepareTeeForTask 63 | @ParameterizedTest 64 | @NullSource 65 | @ValueSource(strings = {"", "0x123", "chainTaskId"}) 66 | void shouldPrepareTeeForTask(String chainTaskId) { 67 | assertTrue(teeGramineService.prepareTeeForTask(chainTaskId)); 68 | 69 | verifyNoInteractions(sgxService, smsClientProvider, teeServicesPropertiesService); 70 | } 71 | // endregion 72 | 73 | // region buildPreComputeDockerEnv 74 | @ParameterizedTest 75 | @NullSource 76 | @ValueSource(strings = {"", "0x123", "chainTaskId"}) 77 | void shouldBuildPreComputeDockerEnv(String chainTaskId) { 78 | final TaskDescription taskDescription = TaskDescription.builder().chainTaskId(chainTaskId).build(); 79 | final List env = teeGramineService.buildPreComputeDockerEnv(taskDescription, TEE_SESSION_GENERATION_RESPONSE); 80 | 81 | assertEquals(2, env.size()); 82 | assertTrue(env.containsAll(List.of( 83 | "sps=http://spsUrl", 84 | "session=0x123_session_id" 85 | ))); 86 | } 87 | // endregion 88 | 89 | // region buildComputeDockerEnv 90 | @ParameterizedTest 91 | @NullSource 92 | @ValueSource(strings = {"", "0x123", "chainTaskId"}) 93 | void shouldBuildComputeDockerEnv(String chainTaskId) { 94 | final TaskDescription taskDescription = TaskDescription.builder().chainTaskId(chainTaskId).build(); 95 | final List env = teeGramineService.buildComputeDockerEnv(taskDescription, TEE_SESSION_GENERATION_RESPONSE); 96 | 97 | assertEquals(2, env.size()); 98 | assertTrue(env.containsAll(List.of( 99 | "sps=http://spsUrl", 100 | "session=0x123_session_id" 101 | ))); 102 | } 103 | // endregion 104 | 105 | // region buildPostComputeDockerEnv 106 | @ParameterizedTest 107 | @NullSource 108 | @ValueSource(strings = {"", "0x123", "chainTaskId"}) 109 | void shouldBuildPostComputeDockerEnv(String chainTaskId) { 110 | final TaskDescription taskDescription = TaskDescription.builder().chainTaskId(chainTaskId).build(); 111 | final List env = teeGramineService.buildPostComputeDockerEnv(taskDescription, TEE_SESSION_GENERATION_RESPONSE); 112 | 113 | assertEquals(2, env.size()); 114 | assertTrue(env.containsAll(List.of( 115 | "sps=http://spsUrl", 116 | "session=0x123_session_id" 117 | ))); 118 | } 119 | // endregion 120 | 121 | // region getAdditionalBindings 122 | @Test 123 | void shouldGetAdditionalBindings() { 124 | final Collection bindings = teeGramineService.getAdditionalBindings(); 125 | 126 | assertEquals(1, bindings.size()); 127 | assertTrue(bindings.contains("/var/run/aesmd/aesm.socket:/var/run/aesmd/aesm.socket")); 128 | } 129 | // endregion 130 | } -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/utils/MaxSizeHashMapTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.iexec.worker.utils; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | class MaxSizeHashMapTests { 27 | @Test 28 | void shouldNotExceedMaxSizeWhenAddSingleValue() { 29 | final int maxSize = 5; 30 | 31 | final Map map = new MaxSizeHashMap<>(maxSize); 32 | 33 | // Add `maxSize` number of values 34 | // `map` should grow each time 35 | for (int i = 0; i < maxSize; i++) { 36 | map.put(i, i); 37 | assertThat(map).hasSize(i + 1); 38 | } 39 | 40 | // `map` should still contain all values 41 | for (int i = 0; i < maxSize; i++) { 42 | assertThat(map).containsKey(i); 43 | } 44 | 45 | // Add new element 46 | // Map should not grow, 47 | // and key "0" should not be accessible anymore 48 | map.put(maxSize, maxSize); 49 | assertThat(map) 50 | .hasSize(maxSize) 51 | .doesNotContainKey(0); 52 | } 53 | 54 | @Test 55 | void shouldNotExceedMaxSizeWhenPutAll() { 56 | final Map map = new MaxSizeHashMap<>(1); 57 | final LinkedHashMap unlimitedMap = new LinkedHashMap<>(); 58 | unlimitedMap.put(0, 0); 59 | unlimitedMap.put(1, 1); 60 | unlimitedMap.put(2, 2); 61 | 62 | map.putAll(unlimitedMap); 63 | 64 | // Map should not have more than a single element: 2 65 | assertThat(map) 66 | .hasSize(1) 67 | .doesNotContainKey(0) 68 | .doesNotContainKey(1) 69 | .containsKey(2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/iexec/worker/version/VersionControllerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2023 IEXEC BLOCKCHAIN TECH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.iexec.worker.version; 17 | 18 | import io.micrometer.core.instrument.Gauge; 19 | import io.micrometer.core.instrument.Metrics; 20 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; 21 | import org.junit.jupiter.api.AfterEach; 22 | import org.junit.jupiter.api.BeforeAll; 23 | import org.junit.jupiter.api.BeforeEach; 24 | import org.junit.jupiter.api.Test; 25 | import org.junit.jupiter.api.extension.ExtendWith; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; 28 | import org.springframework.boot.info.BuildProperties; 29 | import org.springframework.context.annotation.Import; 30 | import org.springframework.http.ResponseEntity; 31 | import org.springframework.test.context.junit.jupiter.SpringExtension; 32 | 33 | import static org.assertj.core.api.Assertions.assertThat; 34 | import static org.junit.jupiter.api.Assertions.assertEquals; 35 | 36 | @ExtendWith(SpringExtension.class) 37 | @Import(ProjectInfoAutoConfiguration.class) 38 | class VersionControllerTests { 39 | 40 | private VersionController versionController; 41 | 42 | @Autowired 43 | private BuildProperties buildProperties; 44 | 45 | @BeforeAll 46 | static void initRegistry() { 47 | Metrics.globalRegistry.add(new SimpleMeterRegistry()); 48 | } 49 | 50 | @BeforeEach 51 | void init() { 52 | versionController = new VersionController(buildProperties); 53 | versionController.initializeGaugeVersion(); 54 | } 55 | 56 | @AfterEach 57 | void afterEach() { 58 | Metrics.globalRegistry.clear(); 59 | } 60 | 61 | @Test 62 | void testVersionController() { 63 | assertEquals(ResponseEntity.ok(buildProperties.getVersion()), versionController.getVersion()); 64 | } 65 | 66 | @Test 67 | void shouldReturnInfoGauge() { 68 | final Gauge info = Metrics.globalRegistry.find(VersionController.METRIC_INFO_GAUGE_NAME).gauge(); 69 | assertThat(info) 70 | .isNotNull() 71 | .extracting(Gauge::getId) 72 | .isNotNull() 73 | .extracting( 74 | id -> id.getTag(VersionController.METRIC_INFO_LABEL_APP_NAME), 75 | id -> id.getTag(VersionController.METRIC_INFO_LABEL_APP_VERSION) 76 | ) 77 | .containsExactly(buildProperties.getName(), buildProperties.getVersion()); 78 | assertThat(info.value()).isEqualTo(VersionController.METRIC_VALUE); 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/test/resources/iExec-RLC-RLC-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iExecBlockchainComputing/iexec-worker/8e9999859690b7687e7e2fb9f379c1438250e357/src/test/resources/iExec-RLC-RLC-icon.png -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/bytes32/output/iexec_out/determinism.iexec: -------------------------------------------------------------------------------- 1 | 0xda9a34f3846cc4434eb31ad870aaf47c8a123225732db003c0c19f3c3f6faa01 2 | -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/callback-directory/output/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "callback-data": "0x0000000000000000000000000000000000000000000000000000000000000001" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/callback-fake/output/iexec_out/callback.iexec: -------------------------------------------------------------------------------- 1 | 0xnothexa -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/callback-no-data/output/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "callback-data": "" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/callback/output/iexec_out/callback.iexec: -------------------------------------------------------------------------------- 1 | 0x0000000000000000000000000000000000000000000000000000016a0caa81920000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000004982f5d9a7000000000000000000000000000000000000000000000000000000000000000094254432d5553442d390000000000000000000000000000000000000000000000 -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-directory-missing/output/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "deterministic-output-path": "/iexec_out" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-directory/output/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "deterministic-output-path": "/iexec_out" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-directory/output/iexec_out/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "deterministic-output-path": "/iexec_out" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-directory/output/iexec_out/result1.txt: -------------------------------------------------------------------------------- 1 | A result -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-directory/output/iexec_out/result2.txt: -------------------------------------------------------------------------------- 1 | Another result -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-file/output/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "deterministic-output-path": "/iexec_out/computing-trace.txt" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-file/output/iexec_out/computed.json: -------------------------------------------------------------------------------- 1 | { 2 | "deterministic-output-path": "/iexec_out/computing-trace.txt" 3 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/deterministic-output-file/output/iexec_out/computing-trace.txt: -------------------------------------------------------------------------------- 1 | Detected face in image1.jpg 2 | Detected face in image4.jpg 3 | Detected face in image5.jpg 4 | End compute -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/notBytes32/output/iexec_out/determinism.iexec: -------------------------------------------------------------------------------- 1 | dummyRandomString 2 | -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/scone-tee-corrupted-file/output/iexec_out/enclaveSig.iexec: -------------------------------------------------------------------------------- 1 | { 2 | "this is" : "a corrupted file", 3 | nihahaha :p 4 | } -------------------------------------------------------------------------------- /src/test/resources/tmp/test-worker/scone-tee/output/iexec_out/enclaveSig.iexec: -------------------------------------------------------------------------------- 1 | { 2 | "result": "0xc746143d64ef1a1f9e280cee70e2866daad3116bfe0e7028a53e500b2c92a6d6", 3 | "resultHash": "0x5ade3c39f9e83db590cbcb03fee7e0ba6c533fa3fb4e72f9320c3e641e38c31e", 4 | "resultSalt": "0x5119fb3770cc545ff3ab0377842ebcd923a3cc02fc4390c285f5368e2b0f3742", 5 | "signature": "0xa025ac611f80112c4827f316f2babd92d983c4ebcd4fdcebc57a6f02fd587c4d503264885f0c5aceeccac04c0f6257831bff57890786ceb01dcb9d191a788d711b" 6 | } -------------------------------------------------------------------------------- /src/test/resources/wiremock/mappings/config-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "GET", 4 | "url": "/config/chain" 5 | }, 6 | "response": { 7 | "status": 200, 8 | "body": "{\"chainId\":255,\"chainNodeUrl\":\"http://localhost:8545\",\"blockTime\":\"PT5s\",\"iexecHubContractAddress\":\"0xC129e7917b7c7DeDfAa5Fff1FB18d5D7050fE8ca\",\"sidechain\":\"true\"}", 9 | "headers": { 10 | "Content-Type": "application/json" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/wiremock/mappings/worker-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "GET", 4 | "url": "/workers/config" 5 | }, 6 | "response": { 7 | "status": 200, 8 | "body": "{\"workerPoolAddress\":\"0x1\",\"schedulerPublicAddress\":\"0x2\",\"configServerUrl\":\"http://localhost:{{request.port}}\",\"resultRepositoryURL\":\"http://localhost:{{request.port}}\",\"askForReplicatePeriod\":\"5000\",\"requiredWorkerVersion\":\"x.y.z\"}", 9 | "headers": { 10 | "Content-Type": "application/json" 11 | }, 12 | "transformers": [ 13 | "response-template" 14 | ] 15 | } 16 | } 17 | --------------------------------------------------------------------------------