Skip to content

Commit 69b2ed0

Browse files
Merge pull request #452 from iExecBlockchainComputing/release/6.2.0
Release/6.2.0
2 parents 7982717 + 6df3fd3 commit 69b2ed0

File tree

49 files changed

+3980
-1767
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3980
-1767
lines changed

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ dependencies {
121121
// mongock
122122
implementation "com.github.cloudyrock.mongock:mongock-spring-v5:${mongockVersion}"
123123
implementation "com.github.cloudyrock.mongock:mongodb-springdata-v2-driver:${mongockVersion}"
124+
125+
// expiring map
126+
implementation 'net.jodah:expiringmap:0.5.10'
127+
128+
// awaitility
129+
testImplementation 'org.awaitility:awaitility:4.0.1'
124130
}
125131

126132
jacoco {

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version=6.1.6
2-
iexecCommonVersion=5.5.1
1+
version=6.2.0
2+
iexecCommonVersion=5.6.0
33
nexusUser=fake
44
nexusPassword=fake

src/main/java/com/iexec/core/chain/IexecHubService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ public boolean isInitializableOnchain(String chainDealId, int taskIndex) {
9292
* @param taskIndex
9393
* @return true if the task is found with the status UNSET, false otherwise.
9494
*/
95+
//TODO Migrate to common
9596
public boolean isTaskInUnsetStatusOnChain(String chainDealId, int taskIndex) {
9697
String generatedChainTaskId = ChainUtils.generateChainTaskId(chainDealId, taskIndex);
97-
Optional<ChainTask> optional = getChainTask(generatedChainTaskId);
98-
return optional.map(chainTask -> chainTask.getStatus().equals(ChainTaskStatus.UNSET))
99-
.orElse(false);
98+
Optional<ChainTask> chainTask = getChainTask(generatedChainTaskId);
99+
return chainTask.isEmpty()
100+
|| ChainTaskStatus.UNSET.equals(chainTask.get().getStatus());
100101
}
101102

102103
/**
@@ -166,7 +167,7 @@ public Date getChainDealFinalDeadline(ChainDeal chainDeal) {
166167
return new Date(startTime + maxTime * 10);
167168
}
168169

169-
170+
@Deprecated
170171
public Optional<Pair<String, ChainReceipt>> initialize(String chainDealId, int taskIndex) {
171172
log.info("Requested initialize [chainDealId:{}, taskIndex:{}, waitingTxCount:{}]", chainDealId, taskIndex, getWaitingTransactionCount());
172173
try {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.core.chain.adapter;
18+
19+
20+
import com.iexec.common.chain.ChainTask;
21+
import com.iexec.common.chain.adapter.CommandStatus;
22+
import com.iexec.common.chain.adapter.args.TaskFinalizeArgs;
23+
import feign.FeignException;
24+
import org.springframework.cloud.openfeign.FeignClient;
25+
import org.springframework.http.ResponseEntity;
26+
import org.springframework.web.bind.annotation.*;
27+
28+
@FeignClient(
29+
name = "BlockchainAdapterClient",
30+
url = "#{blockchainAdapterClientConfig.url}",
31+
configuration = BlockchainAdapterClientFeignConfig.class
32+
)
33+
public interface BlockchainAdapterClient {
34+
35+
@GetMapping("/tasks/{chainTaskId}")
36+
ResponseEntity<ChainTask> getTask(
37+
@PathVariable String chainTaskId) throws FeignException;
38+
39+
@PostMapping("/tasks/initialize")
40+
ResponseEntity<String> requestInitializeTask(
41+
@RequestParam String chainDealId,
42+
@RequestParam int taskIndex) throws FeignException;
43+
44+
@GetMapping("/tasks/initialize/{chainTaskId}/status")
45+
ResponseEntity<CommandStatus> getStatusForInitializeTaskRequest(
46+
@PathVariable String chainTaskId) throws FeignException;
47+
48+
@PostMapping("/tasks/finalize/{chainTaskId}")
49+
ResponseEntity<String> requestFinalizeTask(
50+
@PathVariable String chainTaskId,
51+
@RequestBody TaskFinalizeArgs args)throws FeignException ;
52+
53+
@GetMapping("/tasks/finalize/{chainTaskId}/status")
54+
ResponseEntity<CommandStatus> getStatusForFinalizeTaskRequest(
55+
@PathVariable String chainTaskId) throws FeignException;
56+
57+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.core.chain.adapter;
18+
19+
import lombok.extern.slf4j.Slf4j;
20+
import org.springframework.beans.factory.annotation.Value;
21+
import org.springframework.context.annotation.Configuration;
22+
23+
@Slf4j
24+
@Configuration
25+
public class BlockchainAdapterClientConfig {
26+
27+
@Value("${blockchain-adapter.host}")
28+
private String host;
29+
@Value("${blockchain-adapter.port}")
30+
private int port;
31+
@Value("${blockchain-adapter.user.name}")
32+
private String username;
33+
@Value("${blockchain-adapter.user.password}")
34+
private String password;
35+
36+
public String getUrl() {
37+
return buildHostUrl(host, port);
38+
}
39+
40+
public String getUsername() {
41+
return username;
42+
}
43+
44+
public String getPassword() {
45+
return password;
46+
}
47+
48+
private String buildHostUrl(String host, int port) {
49+
return "http://" + host + ":" + port;
50+
}
51+
52+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.core.chain.adapter;
18+
19+
import feign.auth.BasicAuthRequestInterceptor;
20+
import lombok.extern.slf4j.Slf4j;
21+
import org.springframework.context.annotation.Bean;
22+
23+
@Slf4j
24+
public class BlockchainAdapterClientFeignConfig {
25+
26+
private final BlockchainAdapterClientConfig clientConfig;
27+
28+
public BlockchainAdapterClientFeignConfig(BlockchainAdapterClientConfig clientConfig) {
29+
this.clientConfig = clientConfig;
30+
}
31+
32+
@Bean
33+
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
34+
return new BasicAuthRequestInterceptor(clientConfig.getUsername(),
35+
clientConfig.getPassword());
36+
}
37+
38+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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.core.chain.adapter;
18+
19+
import com.iexec.common.chain.adapter.CommandStatus;
20+
import com.iexec.common.chain.adapter.args.TaskFinalizeArgs;
21+
import lombok.extern.slf4j.Slf4j;
22+
import org.apache.commons.lang3.StringUtils;
23+
import org.springframework.http.ResponseEntity;
24+
import org.springframework.stereotype.Service;
25+
26+
import java.util.Optional;
27+
import java.util.function.Function;
28+
29+
import static java.util.concurrent.TimeUnit.SECONDS;
30+
31+
@Slf4j
32+
@Service
33+
public class BlockchainAdapterService {
34+
35+
public static final int WATCH_PERIOD_SECONDS = 1;//To tune
36+
public static final int MAX_ATTEMPTS = 50;
37+
private final BlockchainAdapterClient blockchainAdapterClient;
38+
39+
public BlockchainAdapterService(BlockchainAdapterClient blockchainAdapterClient) {
40+
this.blockchainAdapterClient = blockchainAdapterClient;
41+
}
42+
43+
/**
44+
* Request on-chain initialization of the task.
45+
*
46+
* @param chainDealId ID of the deal
47+
* @param taskIndex index of the task in the deal
48+
* @return chain task ID is initialization is properly requested
49+
*/
50+
public Optional<String> requestInitialize(String chainDealId, int taskIndex) {
51+
try {
52+
ResponseEntity<String> initializeResponseEntity =
53+
blockchainAdapterClient.requestInitializeTask(chainDealId, taskIndex);
54+
if (initializeResponseEntity.getStatusCode().is2xxSuccessful()
55+
&& !StringUtils.isEmpty(initializeResponseEntity.getBody())) {
56+
String chainTaskId = initializeResponseEntity.getBody();
57+
log.info("Requested initialize [chainTaskId:{}, chainDealId:{}, " +
58+
"taskIndex:{}]", chainTaskId, chainDealId, taskIndex);
59+
return Optional.of(chainTaskId);
60+
}
61+
} catch (Throwable e) {
62+
log.error("Failed to requestInitialize [chainDealId:{}, " +
63+
"taskIndex:{}]", chainDealId, taskIndex, e);
64+
}
65+
return Optional.empty();
66+
}
67+
68+
/**
69+
* Verify if the initialize task command is completed on-chain.
70+
*
71+
* @param chainTaskId ID of the task
72+
* @return true if the tx is mined, false if reverted or empty for other
73+
* cases (too long since still RECEIVED or PROCESSING, adapter error)
74+
*/
75+
public Optional<Boolean> isInitialized(String chainTaskId) {
76+
return isCommandCompleted(blockchainAdapterClient::getStatusForInitializeTaskRequest,
77+
chainTaskId, SECONDS.toMillis(WATCH_PERIOD_SECONDS), MAX_ATTEMPTS, 0);
78+
}
79+
80+
/**
81+
* Request on-chain finalization of the task.
82+
*
83+
* @param chainTaskId ID of the deal
84+
* @param resultLink link of the result to be published on-chain
85+
* @param callbackData optional data for on-chain callback
86+
* @return chain task ID is initialization is properly requested
87+
*/
88+
public Optional<String> requestFinalize(String chainTaskId,
89+
String resultLink,
90+
String callbackData) {
91+
try {
92+
ResponseEntity<String> finalizeResponseEntity =
93+
blockchainAdapterClient.requestFinalizeTask(chainTaskId,
94+
new TaskFinalizeArgs(resultLink, callbackData));
95+
if (finalizeResponseEntity.getStatusCode().is2xxSuccessful()
96+
&& !StringUtils.isEmpty(finalizeResponseEntity.getBody())) {
97+
log.info("Requested finalize [chainTaskId:{}, resultLink:{}, " +
98+
"callbackData:{}]", chainTaskId, resultLink, callbackData);
99+
return Optional.of(chainTaskId);
100+
}
101+
} catch (Throwable e) {
102+
log.error("Failed to requestFinalize [chainTaskId:{}, resultLink:{}, " +
103+
"callbackData:{}]", chainTaskId, resultLink, callbackData, e);
104+
}
105+
return Optional.empty();
106+
}
107+
108+
/**
109+
* Verify if the finalize task command is completed on-chain.
110+
*
111+
* @param chainTaskId ID of the task
112+
* @return true if the tx is mined, false if reverted or empty for other
113+
* cases (too long since still RECEIVED or PROCESSING, adapter error)
114+
*/
115+
public Optional<Boolean> isFinalized(String chainTaskId) {
116+
return isCommandCompleted(blockchainAdapterClient::getStatusForFinalizeTaskRequest,
117+
chainTaskId, SECONDS.toMillis(WATCH_PERIOD_SECONDS), MAX_ATTEMPTS, 0);
118+
}
119+
120+
/**
121+
* Verify if a command sent to the adapter is completed on-chain.
122+
*
123+
* @param getCommandStatusFunction method for checking the command is completed
124+
* @param chainTaskId ID of the task
125+
* @param period period in ms between checks
126+
* @param maxAttempts maximum number of attempts for checking
127+
* @param attempt current attempt number
128+
* @return true if the tx is mined, false if reverted or empty for other
129+
* cases (too long since still RECEIVED or PROCESSING, adapter error)
130+
*/
131+
Optional<Boolean> isCommandCompleted(
132+
Function<String, ResponseEntity<CommandStatus>> getCommandStatusFunction,
133+
String chainTaskId,
134+
long period, int maxAttempts, int attempt) {
135+
if (attempt >= maxAttempts) {
136+
log.error("Reached max retry while waiting command completion " +
137+
"[chainTaskId:{}, maxAttempts:{}]",
138+
chainTaskId, maxAttempts);
139+
return Optional.empty();
140+
}
141+
ResponseEntity<CommandStatus> commandStatusEntity;
142+
try {
143+
commandStatusEntity = getCommandStatusFunction.apply(chainTaskId);
144+
if (!commandStatusEntity.getStatusCode().is2xxSuccessful()
145+
|| commandStatusEntity.getBody() == null) {
146+
return Optional.empty();
147+
}
148+
CommandStatus status = commandStatusEntity.getBody();
149+
if (CommandStatus.SUCCESS.equals(status)
150+
|| CommandStatus.FAILURE.equals(status)) {
151+
return Optional.of(status.equals(CommandStatus.SUCCESS));
152+
}
153+
// RECEIVED, PROCESSING
154+
log.warn("Waiting command completion [chainTaskId:{}, " +
155+
"status:{}, period:{}ms, attempt:{}, maxAttempts:{}]",
156+
chainTaskId, status, period, attempt, maxAttempts);
157+
Thread.sleep(period);
158+
return isCommandCompleted(getCommandStatusFunction, chainTaskId,
159+
period, maxAttempts, attempt + 1);
160+
} catch (Throwable e) {
161+
log.error("Unexpected error while waiting command completion " +
162+
"[chainTaskId:{}, period:{}ms, attempt:{}, maxAttempts:{}]",
163+
chainTaskId, period, attempt, maxAttempts, e);
164+
}
165+
return Optional.empty();
166+
}
167+
168+
}

0 commit comments

Comments
 (0)