Skip to content

Commit 4ed965f

Browse files
authored
When undefined, set final deadline after retention-duration for up to batch-size TEE challenges during cleanup (#281)
1 parent d398c98 commit 4ed965f

File tree

9 files changed

+103
-15
lines changed

9 files changed

+103
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
1111
- Configure the SMS at startup to generate Scone sessions in Hardware or MAA mode. (#275)
1212
- Add configurable cron job to delete expired tasks TEE challenges and Ethereum credentials. (#278)
1313
- Use new `FileHashUtils` API. (#280)
14+
- When undefined, set final deadline after `retention-duration` for up to `batch-size` TEE challenges during cleanup. (#281)
1415

1516
### Quality
1617

@@ -24,7 +25,7 @@ All notable changes to this project will be documented in this file.
2425

2526
- Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#270)
2627
- Upgrade to Gradle 8.10.2. (#271)
27-
- Upgrade to `iexec-commons-poco` 4.1.0-NEXT-SNAPSHOT. (#272)
28+
- Upgrade to H2 database 2.2.224. (#281)
2829

2930
## [[8.6.0]](https://github.com/iExecBlockchainComputing/iexec-sms/releases/tag/v8.6.0) 2024-06-18
3031

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ To support:
6666
| `IEXEC_SECRET_PROVISIONER_ENCLAVE_HOSTNAME` | Secret provisioner server host for retrieving secrets from attested enclaves. Typically used by workers to execute TEE tasks. | Positive integer | `localhost` | `localhost` |
6767
| `IEXEC_SECRET_PROVISIONER_ENCLAVE_PORT`| Secret provisioner server port for retrieving secrets from attested enclaves. | Positive integer | `18765` | `4433` |
6868
| `IEXEC_TEE_CHALLENGE_CLEANUP_CRON` | Cron expression to configure TEE challenges cleanup policy. | String | `@hourly` | `@hourly` |
69+
| `IEXEC_TEE_CHALLENGE_CLEANUP_MAX_BATCH_SIZE` | Max number of TEE challenges whose missing deadline could be set at a given time. | Integer | `500` | `500` |
70+
| `IEXEC_TEE_CHALLENGE_CLEANUP_RETENTION_DURATION` | Retention duration when setting missing final deadline. | Duration | `P5D` | `P5D` |
6971
| `IEXEC_TEE_WORKER_PRE_COMPUTE_IMAGE` | TEE enabled OCI image name for worker pre-compute stage of TEE tasks. | String | | |
7072
| `IEXEC_TEE_WORKER_PRE_COMPUTE_FINGERPRINT` | Fingerprint (aka mrenclave) of the TEE enabled worker pre-compute image. | String | | |
7173
| `IEXEC_TEE_WORKER_PRE_COMPUTE_HEAP_SIZE_GB` | Required heap size for a worker pre-compute enclave (in Giga Bytes). | Positive integer | `3` | `3` |

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ dependencies {
5757
implementation 'org.springframework.retry:spring-retry'
5858
// H2
5959
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
60-
implementation 'com.h2database:h2:2.2.222'
60+
implementation 'com.h2database:h2:2.2.224'
6161

6262
// Spring Doc
6363
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'

src/main/java/com/iexec/sms/tee/challenge/TeeChallengeRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ public interface TeeChallengeRepository extends JpaRepository<TeeChallenge, Stri
2727

2828
@Transactional
2929
void deleteByFinalDeadlineBefore(Instant now);
30+
31+
int countByFinalDeadlineIsNull();
3032
}

src/main/java/com/iexec/sms/tee/challenge/TeeChallengeService.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import com.iexec.sms.blockchain.IexecHubService;
2020
import com.iexec.sms.encryption.EncryptionService;
2121
import com.iexec.sms.secret.MeasuredSecretService;
22+
import com.iexec.sms.tee.config.TeeChallengeCleanupConfiguration;
2223
import lombok.extern.slf4j.Slf4j;
24+
import org.springframework.jdbc.core.JdbcTemplate;
2325
import org.springframework.scheduling.annotation.Scheduled;
2426
import org.springframework.stereotype.Service;
2527

@@ -30,22 +32,28 @@
3032
@Service
3133
public class TeeChallengeService {
3234

35+
private final JdbcTemplate jdbcTemplate;
3336
private final TeeChallengeRepository teeChallengeRepository;
3437
private final EncryptionService encryptionService;
3538
private final IexecHubService iexecHubService;
3639
private final MeasuredSecretService teeChallengesMeasuredSecretService;
3740
private final MeasuredSecretService ethereumCredentialsMeasuredSecretService;
41+
private final TeeChallengeCleanupConfiguration teeChallengeCleanupConfiguration;
3842

39-
public TeeChallengeService(final TeeChallengeRepository teeChallengeRepository,
43+
public TeeChallengeService(final JdbcTemplate jdbcTemplate,
44+
final TeeChallengeRepository teeChallengeRepository,
4045
final EncryptionService encryptionService,
4146
final IexecHubService iexecHubService,
4247
final MeasuredSecretService teeChallengeMeasuredSecretService,
43-
final MeasuredSecretService ethereumCredentialsMeasuredSecretService) {
48+
final MeasuredSecretService ethereumCredentialsMeasuredSecretService,
49+
final TeeChallengeCleanupConfiguration teeChallengeCleanupConfiguration) {
50+
this.jdbcTemplate = jdbcTemplate;
4451
this.teeChallengeRepository = teeChallengeRepository;
4552
this.encryptionService = encryptionService;
4653
this.iexecHubService = iexecHubService;
4754
this.teeChallengesMeasuredSecretService = teeChallengeMeasuredSecretService;
4855
this.ethereumCredentialsMeasuredSecretService = ethereumCredentialsMeasuredSecretService;
56+
this.teeChallengeCleanupConfiguration = teeChallengeCleanupConfiguration;
4957
}
5058

5159
public Optional<TeeChallenge> getOrCreate(String taskId, boolean shouldDecryptKeys) {
@@ -81,17 +89,17 @@ public Optional<TeeChallenge> getOrCreate(String taskId, boolean shouldDecryptKe
8189
}
8290

8391
public void encryptChallengeKeys(TeeChallenge teeChallenge) {
84-
EthereumCredentials credentials = teeChallenge.getCredentials();
92+
final EthereumCredentials credentials = teeChallenge.getCredentials();
8593
if (!credentials.isEncrypted()) {
86-
String encPrivateKey = encryptionService.encrypt(credentials.getPrivateKey());
94+
final String encPrivateKey = encryptionService.encrypt(credentials.getPrivateKey());
8795
credentials.setEncryptedPrivateKey(encPrivateKey);
8896
}
8997
}
9098

9199
public void decryptChallengeKeys(TeeChallenge teeChallenge) {
92-
EthereumCredentials credentials = teeChallenge.getCredentials();
100+
final EthereumCredentials credentials = teeChallenge.getCredentials();
93101
if (credentials.isEncrypted()) {
94-
String privateKey = encryptionService.decrypt(credentials.getPrivateKey());
102+
final String privateKey = encryptionService.decrypt(credentials.getPrivateKey());
95103
credentials.setPlainTextPrivateKey(privateKey);
96104
}
97105
}
@@ -101,10 +109,28 @@ public void decryptChallengeKeys(TeeChallenge teeChallenge) {
101109
* <p>
102110
* The interval between two consecutive executions is based on the {@code @Scheduled} annotation
103111
* and its {@code cron} attribute.
112+
* <p>
113+
* Look up for entries without deadlines in the database.
114+
* If entries are found, set deadline to {@code retentionDuration} in the future
115+
* for up to {@code batchSize} entries.
116+
*
117+
* @see <a href="https://protocol.docs.iex.ec/key-concepts/pay-per-task-model>categories in protocol documentation</a>
104118
*/
105119
@Scheduled(cron = "${tee.challenge.cleanup.cron}")
106120
void cleanExpiredTasksTeeChallenges() {
107-
log.debug("cleanExpiredTasksTeeChallenges");
121+
final long start = System.currentTimeMillis();
108122
teeChallengeRepository.deleteByFinalDeadlineBefore(Instant.now());
123+
final int remaining = teeChallengeRepository.countByFinalDeadlineIsNull();
124+
log.info("cleanExpiredTasksTeeChallenges [duration:{}ms, remaining:{}]",
125+
System.currentTimeMillis() - start, remaining);
126+
if (remaining == 0) {
127+
return;
128+
}
129+
final int updated = jdbcTemplate.update(
130+
"UPDATE \"tee_challenge\" SET \"final_deadline\" = ? WHERE \"final_deadline\" IS NULL FETCH FIRST ? ROWS ONLY",
131+
Instant.now().plus(teeChallengeCleanupConfiguration.getMissingDeadlineRetentionDuration()),
132+
teeChallengeCleanupConfiguration.getMissingDeadlineMaxBatchSize());
133+
log.info("cleanExpiredTasksTeeChallenges [duration:{}ms, updated:{}]",
134+
System.currentTimeMillis() - start, updated);
109135
}
110136
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 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.sms.tee.config;
18+
19+
import lombok.Value;
20+
import org.springframework.boot.context.properties.ConfigurationProperties;
21+
import org.springframework.boot.context.properties.ConstructorBinding;
22+
23+
import java.time.Duration;
24+
25+
@Value
26+
@ConstructorBinding
27+
@ConfigurationProperties(prefix = "tee.challenge.cleanup")
28+
public class TeeChallengeCleanupConfiguration {
29+
String cron;
30+
int missingDeadlineMaxBatchSize;
31+
Duration missingDeadlineRetentionDuration;
32+
}

src/main/resources/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,5 @@ springdoc:
5151

5252
tee:
5353
challenge.cleanup.cron: ${IEXEC_TEE_CHALLENGE_CLEANUP_CRON:@hourly}
54+
challenge.cleanup.missing-deadline-max-batch-size: ${IEXEC_TEE_CHALLENGE_CLEANUP_MAX_BATCH_SIZE:500}
55+
challenge.cleanup.missing-deadline-retention-duration: ${IEXEC_TEE_CHALLENGE_CLEANUP_RETENTION_DURATION:P5D}

src/test/java/com/iexec/sms/config/OpenApiConfigTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
@ExtendWith(SpringExtension.class)
3232
@Import(ProjectInfoAutoConfiguration.class)
33-
class OpenApiConfigTest {
33+
class OpenApiConfigTests {
3434

3535
@Autowired
3636
private BuildProperties buildProperties;

src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@
2020
import com.iexec.sms.blockchain.IexecHubService;
2121
import com.iexec.sms.encryption.EncryptionService;
2222
import com.iexec.sms.secret.MeasuredSecretService;
23+
import com.iexec.sms.tee.config.TeeChallengeCleanupConfiguration;
2324
import org.junit.jupiter.api.BeforeEach;
2425
import org.junit.jupiter.api.Test;
2526
import org.junit.jupiter.api.extension.ExtendWith;
2627
import org.mockito.Mock;
2728
import org.mockito.junit.jupiter.MockitoExtension;
2829
import org.springframework.beans.factory.annotation.Autowired;
2930
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
31+
import org.springframework.jdbc.core.JdbcTemplate;
3032

33+
import javax.persistence.EntityManager;
3134
import java.security.GeneralSecurityException;
35+
import java.time.Duration;
3236
import java.time.Instant;
3337
import java.util.Optional;
3438

@@ -46,6 +50,10 @@ class TeeChallengeServiceTests {
4650

4751
private final Instant finalDeadline = Instant.now().minusMillis(1000);
4852

53+
@Autowired
54+
private EntityManager entityManager;
55+
@Autowired
56+
private JdbcTemplate jdbcTemplate;
4957
@Autowired
5058
private EthereumCredentialsRepository ethereumCredentialsRepository;
5159
@Autowired
@@ -68,12 +76,16 @@ class TeeChallengeServiceTests {
6876
@BeforeEach
6977
void beforeEach() {
7078
teeChallengeRepository.deleteAll();
79+
final TeeChallengeCleanupConfiguration cleanupConfiguration = new TeeChallengeCleanupConfiguration(
80+
"@hourly", 1, Duration.ofMinutes(1));
7181
teeChallengeService = new TeeChallengeService(
82+
jdbcTemplate,
7283
teeChallengeRepository,
7384
encryptionService,
7485
iexecHubService,
7586
teeChallengeMeasuredSecretService,
76-
ethereumCredentialsMeasuredSecretService
87+
ethereumCredentialsMeasuredSecretService,
88+
cleanupConfiguration
7789
);
7890
}
7991

@@ -178,7 +190,7 @@ void shouldDecryptChallengeKeys() {
178190
// region cleanExpiredChallenge
179191
@Test
180192
void shouldPurgeExpiredChallenge() throws GeneralSecurityException {
181-
TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub();
193+
final TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub();
182194
teeChallengeRepository.save(encryptedTeeChallengeStub);
183195

184196
teeChallengeService.cleanExpiredTasksTeeChallenges();
@@ -188,15 +200,26 @@ void shouldPurgeExpiredChallenge() throws GeneralSecurityException {
188200
}
189201

190202
@Test
191-
void shouldNotPurgeExpiredChallenge() throws GeneralSecurityException {
192-
TeeChallenge teeChallenge = new TeeChallenge(TASK_ID, null);
203+
void shouldSetChallengeFinalDeadlineWhenUnset() throws GeneralSecurityException {
204+
final TeeChallenge teeChallenge = new TeeChallenge(TASK_ID, null);
193205
teeChallenge.getCredentials().setEncryptedPrivateKey(ENC_PRIVATE);
194-
teeChallengeRepository.save(teeChallenge);
206+
final TeeChallenge savedChallenge = teeChallengeRepository.save(teeChallenge);
207+
208+
assertThat(teeChallengeRepository.countByFinalDeadlineIsNull()).isOne();
195209

196210
teeChallengeService.cleanExpiredTasksTeeChallenges();
211+
// refresh entity to update cache with new database state
212+
entityManager.refresh(savedChallenge);
197213

198214
assertThat(teeChallengeRepository.count()).isOne();
199215
assertThat(ethereumCredentialsRepository.count()).isOne();
216+
217+
assertThat(teeChallengeRepository.countByFinalDeadlineIsNull()).isZero();
218+
final TeeChallenge currentChallenge = teeChallengeRepository.findByTaskId(TASK_ID).orElseThrow();
219+
assertThat(currentChallenge).isNotNull();
220+
assertThat(currentChallenge.getFinalDeadline())
221+
.isNotNull()
222+
.isAfter(Instant.now());
200223
}
201224
// endregion
202225
}

0 commit comments

Comments
 (0)