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() {