qualia_core_db/services/swarm/
dispatch.rs1use super::executor::JobExecutor;
5use super::job::{JobMode, JobResult, JobSpec};
6use super::settlement::{Escrow, SettlementOutcome};
7use super::verify::{verify, VerificationVerdict, VerifyPolicy};
8use super::SwarmError;
9
10#[derive(Debug, Clone)]
12pub struct DispatchOutcome {
13 pub result: JobResult,
16 pub verdict: VerificationVerdict,
17 pub settlement: Option<SettlementOutcome>,
20}
21
22pub fn run_job(
31 spec: &JobSpec,
32 executor: &dyn JobExecutor,
33 policy: VerifyPolicy,
34 escrow: Option<&mut Escrow>,
35) -> Result<DispatchOutcome, SwarmError> {
36 if !spec.input.is_well_formed() {
37 return Err(SwarmError::InvalidJob);
38 }
39
40 let result = executor.execute(&spec.input)?;
42
43 let verdict = verify(&spec.input, &result, policy);
45
46 let settlement = match (&spec.mode, escrow) {
48 (JobMode::Paid { .. }, Some(esc)) => Some(esc.settle(verdict)?),
49 _ => None,
50 };
51
52 Ok(DispatchOutcome {
53 result,
54 verdict,
55 settlement,
56 })
57}
58
59#[cfg(test)]
60mod tests {
61 use super::super::executor::{JobExecutor, LocalKernelExecutor};
62 use super::super::job::{JobInput, JobMode, JobResult, JobSpec};
63 use super::super::settlement::{Escrow, EscrowState, SettlementOutcome};
64 use super::super::SwarmError;
65 use super::*;
66
67 fn dense_job(mode: JobMode) -> JobSpec {
68 JobSpec::new(
69 mode,
70 JobInput::DenseLinearProduct {
71 m: 2,
72 k: 2,
73 n: 2,
74 a: vec![1.0, 2.0, 3.0, 4.0],
75 b: vec![5.0, 6.0, 7.0, 8.0],
76 },
77 )
78 }
79
80 struct LyingExecutor;
83 impl JobExecutor for LyingExecutor {
84 fn execute(&self, input: &JobInput) -> Result<JobResult, SwarmError> {
85 match input {
86 JobInput::DenseLinearProduct { m, n, .. } => {
87 Ok(JobResult::DenseLinearProduct {
88 c: vec![0.0; m * n],
89 }) }
91 _ => Err(SwarmError::InvalidJob),
92 }
93 }
94 }
95
96 fn paid_mode() -> JobMode {
97 JobMode::Paid {
98 requester_did: 1,
99 provider_did: 2,
100 price_micro_units: 500,
101 }
102 }
103
104 #[test]
105 fn honest_paid_job_verifies_and_pays() {
106 let spec = dense_job(paid_mode());
107 let mut escrow = Escrow::offer(spec.id, 1, 2, 500, "$ilp.solar/pay", false);
108 escrow.hold().unwrap();
109 let out = run_job(
110 &spec,
111 &LocalKernelExecutor,
112 VerifyPolicy::default(),
113 Some(&mut escrow),
114 )
115 .unwrap();
116 assert!(out.verdict.is_verified());
117 assert!(matches!(out.settlement, Some(SettlementOutcome::Pay(_))));
118 assert_eq!(escrow.state, EscrowState::ReleasedToProvider);
119 }
120
121 #[test]
122 fn lying_paid_job_is_rejected_and_refunded_never_paid() {
123 let spec = dense_job(paid_mode());
124 let mut escrow = Escrow::offer(spec.id, 1, 2, 500, "$ilp.solar/pay", false);
125 escrow.hold().unwrap();
126 let out = run_job(
127 &spec,
128 &LyingExecutor,
129 VerifyPolicy::default(),
130 Some(&mut escrow),
131 )
132 .unwrap();
133 assert!(!out.verdict.is_verified());
134 assert!(
135 matches!(out.settlement, Some(SettlementOutcome::Refund { .. })),
136 "a lying provider must never be paid"
137 );
138 assert_eq!(escrow.state, EscrowState::RefundedToRequester);
139 }
140
141 #[test]
142 fn personal_job_runs_with_no_settlement() {
143 let spec = dense_job(JobMode::Personal);
144 let out = run_job(&spec, &LocalKernelExecutor, VerifyPolicy::default(), None).unwrap();
145 assert!(out.verdict.is_verified());
146 assert!(out.settlement.is_none());
147 }
148
149 #[test]
150 fn collaborative_job_runs_with_no_settlement() {
151 let spec = dense_job(JobMode::Collaborative { peers: vec![7, 8] });
152 let out = run_job(&spec, &LocalKernelExecutor, VerifyPolicy::default(), None).unwrap();
153 assert!(out.verdict.is_verified());
154 assert!(out.settlement.is_none());
155 }
156}