diff --git a/.gitignore b/.gitignore index 62a7d3b..c5e3cc9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,4 @@ build /out/ /bin/ -.vscode/ \ No newline at end of file +.vscode/ diff --git a/src/itest/java/com/iexec/blockchain/IntegrationTests.java b/src/itest/java/com/iexec/blockchain/IntegrationTests.java index 142ef51..e43b096 100644 --- a/src/itest/java/com/iexec/blockchain/IntegrationTests.java +++ b/src/itest/java/com/iexec/blockchain/IntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2021-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,7 +52,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.web3j.crypto.Hash; -import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.protocol.exceptions.TransactionException; import org.web3j.utils.Numeric; @@ -160,7 +159,6 @@ private static String getServiceUrl(String serviceHost, int servicePort) { @Test void shouldBeFinalized() throws IOException, TransactionException { - TransactionReceipt receipt; final String dealId = triggerDeal(1); final String chainTaskId = appClient.requestInitializeTask(dealId, 0); @@ -174,18 +172,16 @@ void shouldBeFinalized() throws IOException, TransactionException { final String enclaveSignature = BytesUtils.bytesToString(new byte[65]); final WorkerpoolAuthorization workerpoolAuthorization = mockAuthorization(chainTaskId, enclaveChallenge); - receipt = iexecHubService.contribute( + iexecHubService.contribute( chainTaskId, someBytes32Payload, workerpoolAuthorization.getSignature().getValue(), enclaveChallenge, enclaveSignature); - log.info("contribute {}", receipt); waitStatus(chainTaskId, ChainTaskStatus.REVEALING, MAX_POLLING_ATTEMPTS); - receipt = iexecHubService.reveal(chainTaskId, someBytes32Payload); - log.info("reveal {}", receipt); + iexecHubService.reveal(chainTaskId, someBytes32Payload); waitBeforeFinalizing(chainTaskId); final TaskFinalizeArgs taskFinalizeArgs = new TaskFinalizeArgs("", ""); diff --git a/src/main/java/com/iexec/blockchain/chain/IexecHubService.java b/src/main/java/com/iexec/blockchain/chain/IexecHubService.java index ab3b6f7..2e1fdd6 100644 --- a/src/main/java/com/iexec/blockchain/chain/IexecHubService.java +++ b/src/main/java/com/iexec/blockchain/chain/IexecHubService.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,6 +66,11 @@ public static boolean isByte32(final String hexString) { public TransactionReceipt initializeTask(final String chainDealId, final int taskIndex) throws IOException, TransactionException { + final String chainTaskId = ChainUtils.generateChainTaskId(chainDealId, taskIndex); + if (!isTaskInUnsetStatusOnChain(chainTaskId)) { + log.warn("task is already initialized [chainTaskId:{}]", chainTaskId); + return new TransactionReceipt(); + } final String txData = PoCoDataEncoder.encodeInitialize(chainDealId, taskIndex); final SubmittedTx submittedTx = submit("initialize", txData); return waitForTxMined(submittedTx); diff --git a/src/main/java/com/iexec/blockchain/command/generic/CommandEngine.java b/src/main/java/com/iexec/blockchain/command/generic/CommandEngine.java index 1af6614..5cf901b 100644 --- a/src/main/java/com/iexec/blockchain/command/generic/CommandEngine.java +++ b/src/main/java/com/iexec/blockchain/command/generic/CommandEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.web3j.protocol.core.methods.response.TransactionReceipt; +import java.time.Duration; import java.util.Optional; @Slf4j @@ -31,15 +32,16 @@ public abstract class CommandEngine { private final CommandBlockchain blockchainService; private final CommandStorage updaterService; private final QueueService queueService; + private final Duration backoffDelay; - protected CommandEngine( - CommandBlockchain blockchainService, - CommandStorage updaterService, - QueueService queueService - ) { + protected CommandEngine(final CommandBlockchain blockchainService, + final CommandStorage updaterService, + final QueueService queueService, + final Duration backoffDelay) { this.blockchainService = blockchainService; this.updaterService = updaterService; this.queueService = queueService; + this.backoffDelay = backoffDelay; } /** @@ -85,6 +87,15 @@ public void triggerBlockchainCommand(final A args) { log.info("Processing command [{}]", messageDetails); TransactionReceipt receipt = null; while (attempt < MAX_ATTEMPTS && receipt == null) { + if (attempt != 0) { + try { + log.warn("Backoff delay before retrying tx"); + Thread.sleep(backoffDelay.toMillis()); + } catch (InterruptedException e) { + log.warn("Thread has been interrupted", e); + Thread.currentThread().interrupt(); + } + } attempt++; try { receipt = blockchainService.sendBlockchainCommand(args); diff --git a/src/main/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeService.java b/src/main/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeService.java index fc1c0bc..7990bdf 100644 --- a/src/main/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeService.java +++ b/src/main/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeService.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,19 +20,22 @@ import com.iexec.blockchain.command.generic.CommandEngine; import com.iexec.blockchain.command.generic.CommandStorage; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.time.Duration; + import static com.iexec.blockchain.chain.IexecHubService.isByte32; @Slf4j @Service public class TaskFinalizeService extends CommandEngine { - public TaskFinalizeService( - final TaskFinalizeBlockchainService blockchainService, - final CommandStorage storageService, - final QueueService queueService) { - super(blockchainService, storageService, queueService); + public TaskFinalizeService(final TaskFinalizeBlockchainService blockchainService, + final CommandStorage storageService, + final QueueService queueService, + @Value("${chain.tx-backoff-delay}") final Duration backoffDelay) { + super(blockchainService, storageService, queueService, backoffDelay); } public String start(final String chainTaskId, final String resultLink, final String callbackData) { diff --git a/src/main/java/com/iexec/blockchain/command/task/initialize/TaskInitializeService.java b/src/main/java/com/iexec/blockchain/command/task/initialize/TaskInitializeService.java index bdd1442..26587d7 100644 --- a/src/main/java/com/iexec/blockchain/command/task/initialize/TaskInitializeService.java +++ b/src/main/java/com/iexec/blockchain/command/task/initialize/TaskInitializeService.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2020-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,18 +20,21 @@ import com.iexec.blockchain.command.generic.CommandEngine; import com.iexec.blockchain.command.generic.CommandStorage; import com.iexec.commons.poco.chain.ChainUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.time.Duration; + import static com.iexec.blockchain.chain.IexecHubService.isByte32; @Service public class TaskInitializeService extends CommandEngine { - public TaskInitializeService( - final TaskInitializeBlockchainService blockchainService, - final CommandStorage updaterService, - final QueueService queueService) { - super(blockchainService, updaterService, queueService); + public TaskInitializeService(final TaskInitializeBlockchainService blockchainService, + final CommandStorage updaterService, + final QueueService queueService, + @Value("${chain.tx-backoff-delay}") final Duration backoffDelay) { + super(blockchainService, updaterService, queueService, backoffDelay); } public String start(final String chainDealId, final int taskIndex) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 89e1689..b57ace7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,6 +22,7 @@ chain: gas-price-multiplier: ${IEXEC_GAS_PRICE_MULTIPLIER:1.0} # txs will be sent with networkGasPrice*gasPriceMultiplier, 4.0 means super fast gas-price-cap: ${IEXEC_GAS_PRICE_CAP:22000000000} #in Wei, will be used for txs if networkGasPrice*gasPriceMultiplier > gasPriceCap max-allowed-tx-per-block: ${IEXEC_BLOCKCHAIN_ADAPTER_API_MAX_ALLOWED_TX_PER_BLOCK:1} # 1 or 2 + tx-backoff-delay: PT0.5S wallet: path: ${IEXEC_BLOCKCHAIN_ADAPTER_API_WALLET_PATH:src/main/resources/wallet.json} password: ${IEXEC_BLOCKCHAIN_ADAPTER_API_WALLET_PASSWORD:whatever} diff --git a/src/test/java/com/iexec/blockchain/chain/IexecHubServiceTests.java b/src/test/java/com/iexec/blockchain/chain/IexecHubServiceTests.java index 4f17f7a..c437cea 100644 --- a/src/test/java/com/iexec/blockchain/chain/IexecHubServiceTests.java +++ b/src/test/java/com/iexec/blockchain/chain/IexecHubServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2023-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,6 +103,14 @@ void shouldNotInitializeTask() throws IOException { .isInstanceOf(IOException.class); } + @Test + void shouldDetectAlreadyInitializedTask() throws IOException, TransactionException { + final String initializedChainTaskId = ChainUtils.generateChainTaskId(chainDealId, 0); + doReturn(false).when(iexecHubService).isTaskInUnsetStatusOnChain(initializedChainTaskId); + assertThat(iexecHubService.initializeTask(chainDealId, 0)) + .isEqualTo(new TransactionReceipt()); + } + // endregion @Test diff --git a/src/test/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeServiceTests.java b/src/test/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeServiceTests.java index d390ba9..cfe9868 100644 --- a/src/test/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeServiceTests.java +++ b/src/test/java/com/iexec/blockchain/command/task/finalize/TaskFinalizeServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2024-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +20,17 @@ import com.iexec.blockchain.chain.QueueService; import com.iexec.blockchain.command.generic.CommandName; import com.iexec.blockchain.command.generic.CommandStorage; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.web3j.protocol.core.methods.response.TransactionReceipt; +import java.time.Duration; import java.util.Optional; import java.util.stream.Stream; @@ -46,8 +47,6 @@ class TaskFinalizeServiceTests { "0xe90fc4654b5ea32ad8689091e7610cad7ee5c8b9b1a6e39401b57d90343bfcaa"; private static final String RESULT_LINK = "/ipfs/QmeQHGKFAkEkA5tm3kuXqBM9zz9JorkvCsAJ2bzAAh6NX4"; - @InjectMocks - private TaskFinalizeService taskFinalizeService; @Mock private TaskFinalizeBlockchainService blockchainService; @Mock @@ -55,8 +54,15 @@ class TaskFinalizeServiceTests { @Mock private QueueService queueService; + private TaskFinalizeService taskFinalizeService; private final TaskFinalizeArgs args = new TaskFinalizeArgs(CHAIN_TASK_ID, RESULT_LINK, EMPTY_ADDRESS); + @BeforeEach + void init() { + taskFinalizeService = new TaskFinalizeService( + blockchainService, updaterService, queueService, Duration.ofMillis(100L)); + } + // region start @Test void shouldFinalizeTask() { diff --git a/src/test/java/com/iexec/blockchain/command/task/initialize/TaskInitializeServiceTests.java b/src/test/java/com/iexec/blockchain/command/task/initialize/TaskInitializeServiceTests.java index 1acaa6e..f16c485 100644 --- a/src/test/java/com/iexec/blockchain/command/task/initialize/TaskInitializeServiceTests.java +++ b/src/test/java/com/iexec/blockchain/command/task/initialize/TaskInitializeServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2025 IEXEC BLOCKCHAIN TECH + * Copyright 2021-2026 IEXEC BLOCKCHAIN TECH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,16 +21,17 @@ import com.iexec.blockchain.command.generic.CommandName; import com.iexec.blockchain.command.generic.CommandStorage; import com.iexec.commons.poco.chain.ChainUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.web3j.protocol.core.methods.response.TransactionReceipt; +import java.time.Duration; import java.util.Optional; import java.util.stream.Stream; @@ -46,8 +47,6 @@ class TaskInitializeServiceTests { public static final String CHAIN_TASK_ID = ChainUtils.generateChainTaskId(CHAIN_DEAL_ID, TASK_INDEX); - @InjectMocks - private TaskInitializeService taskInitializeService; @Mock private TaskInitializeBlockchainService blockchainCheckerService; @Mock @@ -55,8 +54,15 @@ class TaskInitializeServiceTests { @Mock private QueueService queueService; + private TaskInitializeService taskInitializeService; private final TaskInitializeArgs args = new TaskInitializeArgs(CHAIN_TASK_ID, CHAIN_DEAL_ID, TASK_INDEX); + @BeforeEach + void init() { + taskInitializeService = new TaskInitializeService( + blockchainCheckerService, updaterService, queueService, Duration.ofMillis(100L)); + } + // region start @Test void shouldInitializeTask() {