Skip to main content

wowlab_sentinel/http/services/
work_context.rs

1//! Authorized work-context retrieval.
2
3use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
4use uuid::Uuid;
5use wowlab_common::{
6    ClaimToken, NodePublicKey, WorkContextHash, WorkContextHashParseError,
7    node_http::NodeWorkContextResponse,
8};
9
10use crate::state::RuntimeState;
11
12#[derive(Debug, thiserror::Error)]
13pub(in crate::http) enum WorkContextError {
14    #[error("invalid work context hash")]
15    InvalidHash(#[source] WorkContextHashParseError),
16    #[error("no matching claim")]
17    NoMatchingClaim,
18}
19
20pub(in crate::http) struct WorkContextInput {
21    job_id: Uuid,
22    hash: WorkContextHash,
23    claim_token: ClaimToken,
24    node_public_key: String,
25}
26
27impl WorkContextInput {
28    pub(in crate::http) fn parse(
29        job_id: Uuid,
30        hash: &str,
31        claim_token: String,
32        node_public_key: &NodePublicKey,
33    ) -> Result<Self, WorkContextError> {
34        Ok(Self {
35            job_id,
36            hash: WorkContextHash::from_hex(hash)?,
37            claim_token: ClaimToken::new(claim_token),
38            node_public_key: node_public_key.to_base64(),
39        })
40    }
41}
42
43pub(in crate::http) struct WorkContextLookup<'a> {
44    runtime: &'a RuntimeState,
45}
46
47impl<'a> WorkContextLookup<'a> {
48    pub(in crate::http) const fn new(runtime: &'a RuntimeState) -> Self {
49        Self { runtime }
50    }
51
52    pub(in crate::http) async fn fetch(
53        &self,
54        input: WorkContextInput,
55    ) -> Result<NodeWorkContextResponse, WorkContextError> {
56        let runtimes = self.runtime.jobs.read().await;
57        let runtime = runtimes
58            .get(&input.job_id)
59            .ok_or(WorkContextError::NoMatchingClaim)?;
60
61        if !runtime.authorize_context(&input.claim_token, &input.hash, &input.node_public_key) {
62            return Err(WorkContextError::NoMatchingClaim);
63        }
64
65        let tournament_payload_bytes = if runtime.payload_bytes.is_empty() {
66            String::new()
67        } else {
68            BASE64_STANDARD.encode(&runtime.payload_bytes)
69        };
70
71        Ok(NodeWorkContextResponse {
72            job_id: input.job_id.to_string(),
73            work_context_hash: runtime.work_context_hash,
74            base_sim_config: runtime.base_sim_config.clone(),
75            tournament_payload_bytes,
76            sentinel_config: runtime.sentinel_config.clone(),
77        })
78    }
79}
80
81impl From<WorkContextHashParseError> for WorkContextError {
82    fn from(error: WorkContextHashParseError) -> Self {
83        Self::InvalidHash(error)
84    }
85}
86
87#[cfg(test)]
88mod tests;