Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file.
- Add and use a non-root user in the dockerfile. (#113)
- Reindent `build.gradle` file. (#114)
- Standardisation of the dockerfile and its location in regard to other java components. (#115)
- Rename `Status` to `CommandStatus` in library. (#117)

### Dependency Upgrades

Expand Down
4 changes: 3 additions & 1 deletion iexec-blockchain-adapter-api-library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ plugins {
dependencies {
implementation "com.iexec.commons:iexec-commons-poco:$iexecCommonsPocoVersion"
implementation "com.iexec.common:iexec-common:$iexecCommonVersion"
implementation 'org.apache.commons:commons-lang3:3.12.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1'
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

Expand Down Expand Up @@ -43,7 +45,7 @@ publishing {
username nexusUser
password nexusPassword
}
url = project.hasProperty("nexusUrl")? project.nexusUrl: ''
url = project.hasProperty("nexusUrl") ? project.nexusUrl : ''
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.iexec.blockchain.api;

import com.iexec.common.chain.adapter.CommandStatus;
import com.iexec.common.chain.adapter.args.TaskFinalizeArgs;
import com.iexec.common.config.PublicChainConfig;
import com.iexec.common.sdk.broker.BrokerOrder;
Expand All @@ -30,6 +29,7 @@
* To create the client, call:
* <pre>FeignBuilder.createBuilder(feignLogLevel)
* .target(BlockchainAdapterApiClient.class, blockchainAdapterUrl)</pre>
*
* @see com.iexec.common.utils.FeignBuilder
*/
public interface BlockchainAdapterApiClient {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2021-2023 IEXEC BLOCKCHAIN TECH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.iexec.blockchain.api;

import lombok.extern.slf4j.Slf4j;

import java.util.function.Function;

@Slf4j
public class BlockchainAdapterService {
/**
* Verify if a command sent to the adapter is completed on-chain.
*
* @param getCommandStatusFunction method for fetching the current command status from the adapter
* @param chainTaskId ID of the task
* @param period period in ms between consecutive checks
* @param maxAttempts maximum number of attempts
* @return true if the tx is mined, false if reverted or empty for other cases.
* (too long since still RECEIVED or PROCESSING, adapter error)
*/
boolean isCommandCompleted(
Function<String, CommandStatus> getCommandStatusFunction,
String chainTaskId, long period, int maxAttempts) {
for (int attempt = 0; attempt < maxAttempts; attempt++) {
try {
CommandStatus status = getCommandStatusFunction.apply(chainTaskId);
if (CommandStatus.SUCCESS == status || CommandStatus.FAILURE == status) {
return CommandStatus.SUCCESS == status;
}
// RECEIVED, PROCESSING
log.warn("Waiting command completion [chainTaskId:{}, status:{}, period:{}ms, attempt:{}, maxAttempts:{}]",
chainTaskId, status, period, attempt, maxAttempts);
} catch (Exception e) {
log.error("Unexpected error while waiting command completion [chainTaskId:{}, period:{}ms, attempt:{}, maxAttempts:{}]",
chainTaskId, period, attempt, maxAttempts, e);
}

try {
Thread.sleep(period);
} catch (InterruptedException e) {
log.error("Polling on blockchain command was interrupted", e);
Thread.currentThread().interrupt();
return false;
}
}
log.error("Reached max retry while waiting command completion [chainTaskId:{}, maxAttempts:{}]",
chainTaskId, maxAttempts);
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 IEXEC BLOCKCHAIN TECH
* Copyright 2022-2023 IEXEC BLOCKCHAIN TECH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,9 +14,9 @@
* limitations under the License.
*/

package com.iexec.blockchain.tool;
package com.iexec.blockchain.api;

public enum Status {
public enum CommandStatus {
RECEIVED,
PROCESSING,
SUCCESS,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2021-2023 IEXEC BLOCKCHAIN TECH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.iexec.blockchain.api;

import feign.FeignException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

class BlockchainAdapterServiceTests {

static final String CHAIN_TASK_ID = "CHAIN_TASK_ID";
static final int PERIOD = 10;
static final int MAX_ATTEMPTS = 3;

@Mock
private BlockchainAdapterApiClient blockchainAdapterClient;
@InjectMocks
private BlockchainAdapterService blockchainAdapterService;

@BeforeEach
void init() {
MockitoAnnotations.openMocks(this);
}

// region isCommandCompleted
@Test
void isCommandCompletedTrueWhenSuccess() {
when(blockchainAdapterClient.getStatusForInitializeTaskRequest(CHAIN_TASK_ID))
.thenReturn(CommandStatus.RECEIVED)
.thenReturn(CommandStatus.PROCESSING)
.thenReturn(CommandStatus.SUCCESS);

boolean commandCompleted = blockchainAdapterService.isCommandCompleted(
blockchainAdapterClient::getStatusForInitializeTaskRequest, CHAIN_TASK_ID, PERIOD, MAX_ATTEMPTS);
assertTrue(commandCompleted);
}

@Test
void isCommandCompletedFalseWhenFailure() {
when(blockchainAdapterClient.getStatusForInitializeTaskRequest(CHAIN_TASK_ID))
.thenReturn(CommandStatus.RECEIVED)
.thenReturn(CommandStatus.PROCESSING)
.thenReturn(CommandStatus.FAILURE);

boolean commandCompleted = blockchainAdapterService.isCommandCompleted(
blockchainAdapterClient::getStatusForInitializeTaskRequest, CHAIN_TASK_ID, PERIOD, MAX_ATTEMPTS);
assertFalse(commandCompleted);
}

@Test
void isCommandCompletedFalseWhenMaxAttempts() {
when(blockchainAdapterClient.getStatusForInitializeTaskRequest(CHAIN_TASK_ID))
.thenReturn(CommandStatus.PROCESSING);
boolean commandCompleted = blockchainAdapterService.isCommandCompleted(
blockchainAdapterClient::getStatusForInitializeTaskRequest, CHAIN_TASK_ID, PERIOD, MAX_ATTEMPTS);
assertFalse(commandCompleted);
}

@Test
void isCommandCompletedFalseWhenFeignException() {
when(blockchainAdapterClient.getStatusForFinalizeTaskRequest(CHAIN_TASK_ID))
.thenThrow(FeignException.class);
boolean commandCompleted = blockchainAdapterService.isCommandCompleted(
blockchainAdapterClient::getStatusForFinalizeTaskRequest, CHAIN_TASK_ID, PERIOD, MAX_ATTEMPTS);
assertFalse(commandCompleted);
}

@Test
void isCommandCompletedFalseWhenInterrupted() throws InterruptedException {
when(blockchainAdapterClient.getStatusForFinalizeTaskRequest(CHAIN_TASK_ID))
.thenReturn(CommandStatus.PROCESSING);
ExecutorService service = Executors.newSingleThreadExecutor();
Future<?> future = service.submit(() ->
blockchainAdapterService.isCommandCompleted(blockchainAdapterClient::getStatusForFinalizeTaskRequest,
CHAIN_TASK_ID, 5000L, MAX_ATTEMPTS));
Thread.sleep(1000L);
future.cancel(true);
assertThrows(CancellationException.class, future::get);
}
// endregion
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 IEXEC BLOCKCHAIN TECH
* Copyright 2020-2023 IEXEC BLOCKCHAIN TECH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,7 +16,7 @@

package com.iexec.blockchain.command.generic;

import com.iexec.blockchain.tool.Status;
import com.iexec.blockchain.api.CommandStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -52,7 +52,7 @@ public abstract class Command<A extends CommandArgs> {
@Version
private Long version;

private Status status;
private CommandStatus status;
private Instant creationDate;
private Instant processingDate;
private Instant finalDate;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 IEXEC BLOCKCHAIN TECH
* Copyright 2020-2023 IEXEC BLOCKCHAIN TECH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,8 +17,8 @@
package com.iexec.blockchain.command.generic;


import com.iexec.blockchain.api.CommandStatus;
import com.iexec.blockchain.tool.QueueService;
import com.iexec.blockchain.tool.Status;
import lombok.extern.slf4j.Slf4j;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

Expand Down Expand Up @@ -116,7 +116,7 @@ public void triggerBlockchainCommand(A args) {
* @param chainObjectId blockchain object ID
* @return status
*/
public Optional<Status> getStatusForCommand(String chainObjectId) {
public Optional<CommandStatus> getStatusForCommand(String chainObjectId) {
return updaterService.getStatusForCommand(chainObjectId);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 IEXEC BLOCKCHAIN TECH
* Copyright 2020-2023 IEXEC BLOCKCHAIN TECH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,8 +16,7 @@

package com.iexec.blockchain.command.generic;


import com.iexec.blockchain.tool.Status;
import com.iexec.blockchain.api.CommandStatus;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -51,7 +50,7 @@ public boolean updateToReceived(A args) {
}

C command = this.newCommandInstance();
command.setStatus(Status.RECEIVED);
command.setStatus(CommandStatus.RECEIVED);
command.setChainObjectId(chainObjectId);
command.setArgs(args);
command.setCreationDate(Instant.now());
Expand All @@ -71,12 +70,12 @@ public boolean updateToProcessing(String chainObjectId) {
Optional<C> localCommand = commandRepository
.findByChainObjectId(chainObjectId)
.filter(command -> command.getStatus() != null)
.filter(command -> command.getStatus() == Status.RECEIVED);
.filter(command -> command.getStatus() == CommandStatus.RECEIVED);
if (localCommand.isEmpty()) {
return false;
}
C command = localCommand.get();
command.setStatus(Status.PROCESSING);
command.setStatus(CommandStatus.PROCESSING);
command.setProcessingDate(Instant.now());
commandRepository.save(command);
return true;
Expand All @@ -95,23 +94,23 @@ public void updateToFinal(String chainObjectId,
Optional<C> localCommand = commandRepository
.findByChainObjectId(chainObjectId)
.filter(command -> command.getStatus() != null)
.filter(command -> command.getStatus() == Status.PROCESSING);
.filter(command -> command.getStatus() == CommandStatus.PROCESSING);
if (localCommand.isEmpty()) {
return;
}
C command = localCommand.get();

Status status;
CommandStatus status;
if (StringUtils.isNotEmpty(receipt.getStatus())
&& receipt.getStatus().equals("0x1")) {
status = Status.SUCCESS;
status = CommandStatus.SUCCESS;
log.info("Success command with transaction receipt " +
"[chainObjectId:{}, command:{}, receipt:{}]",
chainObjectId,
command.getClass().getSimpleName(),
receipt);
} else {
status = Status.FAILURE;
status = CommandStatus.FAILURE;
log.info("Failure after transaction sent [chainObjectId:{}, " +
"command:{}, receipt:{}]", chainObjectId,
command.getClass().getSimpleName(), receipt);
Expand All @@ -128,7 +127,7 @@ public void updateToFinal(String chainObjectId,
* @param chainObjectId blockchain object ID on which the blockchain command
* is performed
*/
public Optional<Status> getStatusForCommand(String chainObjectId) {
public Optional<CommandStatus> getStatusForCommand(String chainObjectId) {
return commandRepository.findByChainObjectId(chainObjectId)
.map(Command::getStatus);
}
Expand Down
Loading