wowlab_sentinel/http/services/
work_context.rs1use 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;