Skip to content

Commit 4f8051a

Browse files
authored
feat: return list of errors in new format to worker (#22)
1 parent 7491cf4 commit 4f8051a

File tree

6 files changed

+140
-121
lines changed

6 files changed

+140
-121
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

post-compute/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ serde = "1.0.219"
2222
serde_json = "1.0.140"
2323
sha256 = "1.6.0"
2424
sha3 = "0.10.8"
25+
strum = "0.27.2"
26+
strum_macros = "0.27.2"
2527
thiserror = "2.0.12"
2628
walkdir = "2.5.0"
2729
zip = "4.0.0"

post-compute/src/api/worker_api.rs

Lines changed: 51 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,6 @@ use crate::compute::{
55
};
66
use log::error;
77
use reqwest::{blocking::Client, header::AUTHORIZATION};
8-
use serde::Serialize;
9-
10-
/// Represents payload that can be sent to the worker API to report the outcome of the
11-
/// post‑compute stage.
12-
///
13-
/// The JSON structure expected by the REST endpoint is:
14-
/// ```json
15-
/// {
16-
/// "cause": "<ReplicateStatusCause as string>"
17-
/// }
18-
/// ```
19-
///
20-
/// # Arguments
21-
///
22-
/// * `cause` - A reference to the ReplicateStatusCause indicating why the post-compute operation exited
23-
///
24-
/// # Example
25-
///
26-
/// ```rust
27-
/// use tee_worker_post_compute::{
28-
/// api::worker_api::ExitMessage,
29-
/// compute::errors::ReplicateStatusCause,
30-
/// };
31-
///
32-
/// let exit_message = ExitMessage::from(&ReplicateStatusCause::PostComputeInvalidTeeSignature);
33-
/// ```
34-
#[derive(Serialize, Debug)]
35-
pub struct ExitMessage<'a> {
36-
pub cause: &'a ReplicateStatusCause,
37-
}
38-
39-
impl<'a> From<&'a ReplicateStatusCause> for ExitMessage<'a> {
40-
fn from(cause: &'a ReplicateStatusCause) -> Self {
41-
Self { cause }
42-
}
43-
}
448

459
/// Thin wrapper around a [`Client`] that knows how to reach the iExec worker API.
4610
///
@@ -96,21 +60,21 @@ impl WorkerApiClient {
9660
Self::new(&base_url)
9761
}
9862

99-
/// Sends an exit cause for a post-compute operation to the Worker API.
63+
/// Sends exit causes for a post-compute operation to the Worker API.
10064
///
101-
/// This method reports the exit cause of a post-compute operation to the Worker API,
65+
/// This method reports the exit causes of a post-compute operation to the Worker API,
10266
/// which can be used for tracking and debugging purposes.
10367
///
10468
/// # Arguments
10569
///
10670
/// * `authorization` - The authorization token to use for the API request
107-
/// * `chain_task_id` - The chain task ID for which to report the exit cause
108-
/// * `exit_cause` - The exit cause to report
71+
/// * `chain_task_id` - The chain task ID for which to report the exit causes
72+
/// * `exit_causes` - The exit causes to report
10973
///
11074
/// # Returns
11175
///
112-
/// * `Ok(())` - If the exit cause was successfully reported
113-
/// * `Err(ReplicateStatusCause)` - If the exit cause could not be reported due to an HTTP error
76+
/// * `Ok(())` - If the exit causes were successfully reported
77+
/// * `Err(ReplicateStatusCause)` - If the exit causes could not be reported due to an HTTP error
11478
///
11579
/// # Errors
11680
///
@@ -121,34 +85,34 @@ impl WorkerApiClient {
12185
///
12286
/// ```rust
12387
/// use tee_worker_post_compute::{
124-
/// api::worker_api::{ExitMessage, WorkerApiClient},
88+
/// api::worker_api::WorkerApiClient,
12589
/// compute::errors::ReplicateStatusCause,
12690
/// };
12791
///
12892
/// let client = WorkerApiClient::new("http://worker:13100");
129-
/// let exit_message = ExitMessage::from(&ReplicateStatusCause::PostComputeInvalidTeeSignature);
93+
/// let exit_causes = vec![ReplicateStatusCause::PostComputeInvalidTeeSignature];
13094
///
131-
/// match client.send_exit_cause_for_post_compute_stage(
95+
/// match client.send_exit_causes_for_post_compute_stage(
13296
/// "authorization_token",
13397
/// "0x123456789abcdef",
134-
/// &exit_message,
98+
/// &exit_causes,
13599
/// ) {
136-
/// Ok(()) => println!("Exit cause reported successfully"),
137-
/// Err(error) => eprintln!("Failed to report exit cause: {}", error),
100+
/// Ok(()) => println!("Exit causes reported successfully"),
101+
/// Err(error) => eprintln!("Failed to report exit causes: {}", error),
138102
/// }
139103
/// ```
140-
pub fn send_exit_cause_for_post_compute_stage(
104+
pub fn send_exit_causes_for_post_compute_stage(
141105
&self,
142106
authorization: &str,
143107
chain_task_id: &str,
144-
exit_cause: &ExitMessage,
108+
exit_causes: &[ReplicateStatusCause],
145109
) -> Result<(), ReplicateStatusCause> {
146-
let url = format!("{}/compute/post/{chain_task_id}/exit", self.base_url);
110+
let url = format!("{}/compute/post/{chain_task_id}/exit-causes", self.base_url);
147111
match self
148112
.client
149113
.post(&url)
150114
.header(AUTHORIZATION, authorization)
151-
.json(exit_cause)
115+
.json(exit_causes)
152116
.send()
153117
{
154118
Ok(response) => {
@@ -158,13 +122,13 @@ impl WorkerApiClient {
158122
let status = response.status();
159123
let body = response.text().unwrap_or_default();
160124
error!(
161-
"Failed to send exit cause to worker: [status:{status:?}, body:{body:#?}]"
125+
"Failed to send exit causes to worker: [status:{status:?}, body:{body:#?}]"
162126
);
163127
Err(ReplicateStatusCause::PostComputeFailedUnknownIssue)
164128
}
165129
}
166130
Err(e) => {
167-
error!("An error occured while sending exit cause to worker: {e}");
131+
error!("An error occured while sending exit causes to worker: {e}");
168132
Err(ReplicateStatusCause::PostComputeFailedUnknownIssue)
169133
}
170134
}
@@ -266,36 +230,30 @@ mod tests {
266230
const CHALLENGE: &str = "challenge";
267231
const CHAIN_TASK_ID: &str = "0x123456789abcdef";
268232

269-
// region ExitMessage()
233+
// region serialize List of ReplicateStatusCause
270234
#[test]
271-
fn should_serialize_exit_message() {
272-
let causes = [
273-
(
274-
ReplicateStatusCause::PostComputeInvalidTeeSignature,
275-
"POST_COMPUTE_INVALID_TEE_SIGNATURE",
276-
),
277-
(
278-
ReplicateStatusCause::PostComputeWorkerAddressMissing,
279-
"POST_COMPUTE_WORKER_ADDRESS_MISSING",
280-
),
281-
(
282-
ReplicateStatusCause::PostComputeFailedUnknownIssue,
283-
"POST_COMPUTE_FAILED_UNKNOWN_ISSUE",
284-
),
235+
fn replicate_status_cause_serializes_as_json_array_when_multiple_causes() {
236+
let causes = vec![
237+
ReplicateStatusCause::PostComputeInvalidTeeSignature,
238+
ReplicateStatusCause::PostComputeWorkerAddressMissing,
285239
];
240+
let serialized = to_string(&causes).expect("Failed to serialize");
241+
let expected = r#"[{"cause":"POST_COMPUTE_INVALID_TEE_SIGNATURE","message":"Invalid TEE signature"},{"cause":"POST_COMPUTE_WORKER_ADDRESS_MISSING","message":"Worker address not found in TEE session"}]"#;
242+
assert_eq!(serialized, expected);
243+
}
286244

287-
for (cause, message) in causes {
288-
let exit_message = ExitMessage::from(&cause);
289-
let serialized = to_string(&exit_message).expect("Failed to serialize");
290-
let expected = format!("{{\"cause\":\"{message}\"}}");
291-
assert_eq!(serialized, expected);
292-
}
245+
#[test]
246+
fn replicate_status_cause_serializes_as_json_array_when_single_cause() {
247+
let causes = vec![ReplicateStatusCause::PostComputeFailedUnknownIssue];
248+
let serialized = to_string(&causes).expect("Failed to serialize");
249+
let expected = r#"[{"cause":"POST_COMPUTE_FAILED_UNKNOWN_ISSUE","message":"Unexpected error occurred"}]"#;
250+
assert_eq!(serialized, expected);
293251
}
294252
// endregion
295253

296254
// region get_worker_api_client
297255
#[test]
298-
fn should_get_worker_api_client_with_env_var() {
256+
fn from_env_creates_client_with_custom_url_when_env_var_set() {
299257
with_vars(
300258
vec![(WorkerHostEnvVar.name(), Some("custom-worker-host:9999"))],
301259
|| {
@@ -306,26 +264,24 @@ mod tests {
306264
}
307265

308266
#[test]
309-
fn should_get_worker_api_client_without_env_var() {
267+
fn from_env_creates_client_with_default_url_when_env_var_missing() {
310268
with_vars(vec![(WorkerHostEnvVar.name(), None::<&str>)], || {
311269
let client = WorkerApiClient::from_env();
312270
assert_eq!(client.base_url, format!("http://{DEFAULT_WORKER_HOST}"));
313271
});
314272
}
315273
// endregion
316274

317-
// region send_exit_cause_for_post_compute_stage()
275+
// region send_exit_causes_for_post_compute_stage()
318276
#[tokio::test]
319-
async fn should_send_exit_cause() {
277+
async fn send_exit_causes_for_post_compute_stage_succeeds_when_server_responds_ok() {
320278
let mock_server = MockServer::start().await;
321279
let server_url = mock_server.uri();
322280

323-
let expected_body = json!({
324-
"cause": ReplicateStatusCause::PostComputeInvalidTeeSignature,
325-
});
281+
let expected_body = json!([ReplicateStatusCause::PostComputeInvalidTeeSignature,]);
326282

327283
Mock::given(method("POST"))
328-
.and(path(format!("/compute/post/{CHAIN_TASK_ID}/exit")))
284+
.and(path(format!("/compute/post/{CHAIN_TASK_ID}/exit-causes")))
329285
.and(header("Authorization", CHALLENGE))
330286
.and(body_json(&expected_body))
331287
.respond_with(ResponseTemplate::new(200))
@@ -334,13 +290,12 @@ mod tests {
334290
.await;
335291

336292
let result = tokio::task::spawn_blocking(move || {
337-
let exit_message =
338-
ExitMessage::from(&ReplicateStatusCause::PostComputeInvalidTeeSignature);
293+
let exit_causes = vec![ReplicateStatusCause::PostComputeInvalidTeeSignature];
339294
let worker_api_client = WorkerApiClient::new(&server_url);
340-
worker_api_client.send_exit_cause_for_post_compute_stage(
295+
worker_api_client.send_exit_causes_for_post_compute_stage(
341296
CHALLENGE,
342297
CHAIN_TASK_ID,
343-
&exit_message,
298+
&exit_causes,
344299
)
345300
})
346301
.await
@@ -351,7 +306,7 @@ mod tests {
351306

352307
#[tokio::test]
353308
#[serial]
354-
async fn should_not_send_exit_cause() {
309+
async fn send_exit_causes_for_post_compute_stage_fails_when_server_returns_404() {
355310
{
356311
let mut logger = TEST_LOGGER.lock().unwrap();
357312
while logger.pop().is_some() {}
@@ -360,20 +315,19 @@ mod tests {
360315
let server_url = mock_server.uri();
361316

362317
Mock::given(method("POST"))
363-
.and(path(format!("/compute/post/{CHAIN_TASK_ID}/exit")))
318+
.and(path(format!("/compute/post/{CHAIN_TASK_ID}/exit-causes")))
364319
.respond_with(ResponseTemplate::new(404))
365320
.expect(1)
366321
.mount(&mock_server)
367322
.await;
368323

369324
let result = tokio::task::spawn_blocking(move || {
370-
let exit_message =
371-
ExitMessage::from(&ReplicateStatusCause::PostComputeFailedUnknownIssue);
325+
let exit_causes = vec![ReplicateStatusCause::PostComputeFailedUnknownIssue];
372326
let worker_api_client = WorkerApiClient::new(&server_url);
373-
worker_api_client.send_exit_cause_for_post_compute_stage(
327+
worker_api_client.send_exit_causes_for_post_compute_stage(
374328
CHALLENGE,
375329
CHAIN_TASK_ID,
376-
&exit_message,
330+
&exit_causes,
377331
)
378332
})
379333
.await
@@ -402,7 +356,7 @@ mod tests {
402356

403357
// region send_computed_file_to_host()
404358
#[tokio::test]
405-
async fn should_send_computed_file_successfully() {
359+
async fn send_computed_file_to_host_succeeds_when_server_responds_ok() {
406360
let mock_server = MockServer::start().await;
407361
let server_uri = mock_server.uri();
408362

@@ -437,7 +391,7 @@ mod tests {
437391

438392
#[tokio::test]
439393
#[serial]
440-
async fn should_fail_send_computed_file_on_server_error() {
394+
async fn send_computed_file_to_host_fails_when_server_returns_500() {
441395
{
442396
let mut logger = TEST_LOGGER.lock().unwrap();
443397
while logger.pop().is_some() {}
@@ -491,7 +445,7 @@ mod tests {
491445

492446
#[tokio::test]
493447
#[serial]
494-
async fn should_handle_invalid_chain_task_id_in_url() {
448+
async fn send_computed_file_to_host_fails_when_chain_task_id_invalid() {
495449
{
496450
let mut logger = TEST_LOGGER.lock().unwrap();
497451
while logger.pop().is_some() {}
@@ -532,7 +486,7 @@ mod tests {
532486
}
533487

534488
#[tokio::test]
535-
async fn should_send_computed_file_with_minimal_data() {
489+
async fn send_computed_file_to_host_succeeds_when_minimal_data_provided() {
536490
let mock_server = MockServer::start().await;
537491
let server_uri = mock_server.uri();
538492

0 commit comments

Comments
 (0)