1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use std::path::PathBuf;
use std::time::Duration;
use crate::{Result, SdkError};
use zisk_coordinator_api::dto::{DomainJobKindResponse, TerminalStatus};
use zisk_rom_setup::{get_elf_bin_verkey_file_path_with_hash, get_output_path, HashMode};
use crate::job_handle::{new_subscriber_list, JobHandle, JobId};
use crate::lifecycle::SetupTarget;
use crate::{Client, ClientSync};
pub struct SetupResult {
pub job_id: Option<JobId>,
}
impl SetupResult {
pub fn job_id(&self) -> Option<&JobId> {
self.job_id.as_ref()
}
}
/// Builder for a program or recurser setup request.
///
/// Obtain via `client.setup(&program)` or `client.setup(&recurser)`.
/// - Embedded: runs ROM / recurser setup locally, idempotent.
/// - Remote: dispatches setup work to workers via the coordinator.
pub struct SetupRequest<'a, C> {
client: &'a C,
target: SetupTarget<'a>,
with_hints: bool,
emulator_only: bool,
timeout: Option<Duration>,
output_dir: Option<PathBuf>,
}
#[allow(private_bounds)]
impl<'a, C: Client> SetupRequest<'a, C> {
pub(crate) fn new(client: &'a C, target: SetupTarget<'a>) -> Self {
Self {
client,
target,
with_hints: false,
emulator_only: false,
timeout: None,
output_dir: None,
}
}
/// Enable hints during ROM setup. Requires Assembly executor on the client
/// builder. Only applies to [`SetupTarget::Program`].
#[must_use]
pub fn with_hints(mut self) -> Self {
self.with_hints = true;
self
}
/// Generate setup for emulator only (skips ASM service startup).
#[must_use]
pub fn emulator_only(mut self) -> Self {
self.emulator_only = true;
self
}
/// Set a timeout for the setup job.
#[must_use]
pub fn timeout(mut self, duration: Duration) -> Self {
self.timeout = Some(duration);
self
}
/// Set the directory where the verkey file will be stored after setup
/// completes. Only applies to [`SetupTarget::Program`] — recurser
/// artifacts are written to an SDK-managed location.
#[must_use]
pub fn output_dir(mut self, dir: PathBuf) -> Self {
self.output_dir = Some(dir);
self
}
/// Submit the setup, returning a [`JobHandle<SetupResult>`].
pub fn run(self) -> Result<JobHandle<SetupResult>> {
let subs = new_subscriber_list();
match self.target {
SetupTarget::Program(program) => {
let mut handle = self.client.run_setup(
program,
self.with_hints,
self.emulator_only,
self.timeout,
subs,
)?;
let hash_id = program.program_id.hash_id.to_string();
let output_dir = self.output_dir.clone();
handle.set_pre_process(move |status: &TerminalStatus| {
if let TerminalStatus::Completed(DomainJobKindResponse::Setup {
vk,
hash_mode,
}) = status
{
// The hash mode is dictated by the worker's proving key, not
// the client; use the authoritative value returned with the
// setup to name the verkey artifact.
let hash_mode = hash_mode.parse::<HashMode>().map_err(SdkError::backend)?;
let output_path =
get_output_path(&output_dir).map_err(SdkError::backend)?;
let path = get_elf_bin_verkey_file_path_with_hash(
&hash_id,
&output_path,
hash_mode,
)
.map_err(SdkError::backend)?;
std::fs::write(&path, vk)?;
}
Ok(())
});
Ok(handle)
}
SetupTarget::Recurser(agg) => {
let mut handle =
self.client.run_setup_aggregation_program(agg, self.timeout, subs)?;
// Fill `agg.vk_cache` from the terminal response so a later
// `agg.vk()` doesn't fall through to a disk read.
let agg_clone = agg.clone();
handle.set_pre_process(move |status: &TerminalStatus| {
if let TerminalStatus::Completed(
DomainJobKindResponse::SetupAggregationProgram { vk, hash_mode },
) = status
{
if vk.len() != 32 {
return Err(SdkError::Recurser(format!(
"coordinator returned a {}-byte recurser verkey; expected 32",
vk.len()
)));
}
// The hash mode is dictated by the worker's proving key;
// it must travel with the verkey so a later verify can
// match it against the proof's hash family.
let hash_mode = hash_mode.parse::<HashMode>().map_err(SdkError::backend)?;
let mut limbs = [0u64; 4];
for i in 0..4 {
let chunk: [u8; 8] = vk[i * 8..(i + 1) * 8].try_into().unwrap();
limbs[i] = u64::from_le_bytes(chunk);
}
let _ = agg_clone
.vk_cache
.set(zisk_common::ProgramVK { vk: limbs.to_vec(), hash_mode });
}
Ok(())
});
Ok(handle)
}
}
}
}
#[allow(private_bounds)]
impl<'a, C: ClientSync> SetupRequest<'a, C> {
/// Run ROM setup synchronously, returning the result directly.
///
/// Unlike [`run`](Self::run), this drives the work on the calling thread and
/// requires no async runtime — use it when embedding the SDK in a
/// synchronous program. Available only for the embedded client
/// ([`EmbeddedClient`](crate::EmbeddedClient)).
pub fn run_sync(self) -> Result<SetupResult> {
let subs = new_subscriber_list();
match self.target {
SetupTarget::Program(program) => {
self.client.run_setup_sync(program, self.with_hints, self.emulator_only, subs)
}
SetupTarget::Recurser(agg) => self.client.run_setup_aggregation_program_sync(agg, subs),
}
}
}