1use std::path::{Path, PathBuf};
8use std::time::Duration;
9
10use crate::client::{ClientBuilder, OffChainClientConfigBuilder};
11use crate::config::{
12 MpcConfig, MpcConfigSummary, NetworkConfig, NetworkConfigSummary, NetworkDeployment,
13};
14use crate::error::{Error, Result};
15use crate::program::{BytecodeSummary, Program, ProgramSummary};
16use crate::server::ServerBuilder;
17use crate::types::{ProgramArgs, Value};
18use crate::vm;
19
20#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
21pub struct RuntimeSummary {
22 pub program: ProgramSummary,
23 pub mpc: Option<MpcConfigSummary>,
24 pub network: Option<NetworkConfigSummary>,
25 pub named_input_count: usize,
26 pub client_input_count: usize,
27 pub expected_clients: Option<usize>,
28 pub local_runner_configured: bool,
29}
30
31#[derive(Debug, Clone)]
32pub struct StoffelRuntime {
33 program: Program,
34 mpc_config: Option<MpcConfig>,
35 network_config: Option<NetworkConfig>,
36 local_runner_path: Option<PathBuf>,
37 inputs: Vec<(String, Value)>,
38 client_inputs: Vec<(u64, Vec<Value>)>,
39 expected_clients: Option<usize>,
40 client_output_counts: std::collections::HashMap<u64, u64>,
44}
45
46impl StoffelRuntime {
47 pub(crate) fn new(
48 program: Program,
49 mpc_config: Option<MpcConfig>,
50 network_config: Option<NetworkConfig>,
51 local_runner_path: Option<PathBuf>,
52 inputs: Vec<(String, Value)>,
53 client_inputs: Vec<(u64, Vec<Value>)>,
54 expected_clients: Option<usize>,
55 ) -> Self {
56 Self {
57 program,
58 mpc_config,
59 network_config,
60 local_runner_path,
61 inputs,
62 client_inputs,
63 expected_clients,
64 client_output_counts: std::collections::HashMap::new(),
65 }
66 }
67
68 pub(crate) fn set_client_output_counts(&mut self, counts: std::collections::HashMap<u64, u64>) {
70 self.client_output_counts = counts;
71 }
72
73 pub(crate) fn client_output_counts(&self) -> &std::collections::HashMap<u64, u64> {
75 &self.client_output_counts
76 }
77
78 pub fn program(&self) -> &Program {
79 &self.program
80 }
81
82 pub(crate) fn with_program_and_client_inputs(
83 mut self,
84 program: Program,
85 client_inputs: Vec<(u64, Vec<Value>)>,
86 ) -> Self {
87 self.program = program;
88 self.inputs.clear();
89 self.client_inputs = client_inputs;
90 self
91 }
92
93 pub fn to_bytecode(&self) -> Result<Vec<u8>> {
95 self.program.to_bytecode()
96 }
97
98 pub fn save_bytecode(&self, path: impl AsRef<Path>) -> Result<()> {
100 self.program.save_bytecode(path)
101 }
102
103 pub fn bytecode_summary(&self) -> Result<BytecodeSummary> {
105 self.program.bytecode_summary()
106 }
107
108 pub fn client(&self) -> ClientBuilder {
109 let builder = ClientBuilder::new().with_program(self.program.clone());
110 match &self.network_config {
111 Some(config) => match config.server_addresses() {
112 Ok(addresses) => builder.servers(addresses),
113 Err(error) => builder.configuration_error(error.to_string()),
114 },
115 None => builder,
116 }
117 }
118
119 pub fn client_for_deployment(&self, deployment: &NetworkDeployment) -> ClientBuilder {
121 ClientBuilder::new()
122 .with_program(self.program.clone())
123 .network_deployment(deployment)
124 }
125
126 pub fn offchain_client_config(&self, client_slot: u64) -> Result<OffChainClientConfigBuilder> {
132 let client = self.program.client(client_slot).ok_or_else(|| {
133 Error::Configuration(format!(
134 "program does not declare ClientStore metadata for client slot {client_slot}"
135 ))
136 })?;
137 let input_start_index = self
138 .program
139 .clients()
140 .take_while(|schema| schema.client_slot() != client_slot)
141 .map(|schema| schema.input_count() as u64)
142 .sum();
143 let mpc_config = self
144 .mpc_config
145 .as_ref()
146 .ok_or_else(|| Error::Configuration("MPC configuration is required".to_owned()))?;
147 Ok(OffChainClientConfigBuilder::default()
148 .client_slot(client_slot)
149 .input_start_index(input_start_index)
150 .parties(mpc_config.parties)
151 .threshold(mpc_config.threshold)
152 .backend(mpc_config.backend)
153 .input_types(client.inputs().iter().copied())
154 .output_types(client.outputs().iter().copied()))
155 }
156
157 pub fn server(&self, party_id: usize) -> ServerBuilder {
158 let mut builder = ServerBuilder::new(party_id).with_program(self.program.clone());
159 if let Some(config) = &self.mpc_config {
160 builder = builder.mpc_config(config);
161 }
162 match &self.network_config {
163 Some(config) if config.network.party_id == party_id => builder.network_config(config),
164 Some(config) => builder.configuration_error(format!(
165 "network config party_id {} does not match requested server party_id {party_id}",
166 config.network.party_id
167 )),
168 None => builder,
169 }
170 }
171
172 pub fn server_for_config(&self, config: &NetworkConfig) -> ServerBuilder {
174 let mut builder = ServerBuilder::new(config.party_id()).with_program(self.program.clone());
175 if let Some(mpc_config) = &self.mpc_config {
176 builder = builder.mpc_config(mpc_config);
177 }
178 builder.network_config(config)
179 }
180
181 pub fn servers_for_deployment(&self, deployment: &NetworkDeployment) -> Vec<ServerBuilder> {
186 deployment
187 .configs()
188 .iter()
189 .map(|config| self.server_for_config(config))
190 .collect()
191 }
192
193 pub fn mpc_config(&self) -> Option<&MpcConfig> {
194 self.mpc_config.as_ref()
195 }
196
197 pub fn network_config(&self) -> Option<&NetworkConfig> {
198 self.network_config.as_ref()
199 }
200
201 pub fn mpc_summary(&self) -> Result<Option<MpcConfigSummary>> {
202 self.mpc_config.as_ref().map(MpcConfig::summary).transpose()
203 }
204
205 pub fn network_summary(&self) -> Result<Option<NetworkConfigSummary>> {
206 self.network_config
207 .as_ref()
208 .map(NetworkConfig::summary)
209 .transpose()
210 }
211
212 pub fn summary(&self) -> Result<RuntimeSummary> {
213 Ok(RuntimeSummary {
214 program: self.program.summary(),
215 mpc: self.mpc_summary()?,
216 network: self.network_summary()?,
217 named_input_count: self.inputs.len(),
218 client_input_count: self.client_inputs.len(),
219 expected_clients: self.expected_clients,
220 local_runner_configured: self.local_runner_path.is_some(),
221 })
222 }
223
224 pub fn local_runner_binary_path(&self) -> Option<&Path> {
225 self.local_runner_path.as_deref()
226 }
227
228 pub fn inputs(&self) -> &[(String, Value)] {
229 &self.inputs
230 }
231
232 pub fn client_inputs(&self) -> &[(u64, Vec<Value>)] {
233 &self.client_inputs
234 }
235
236 pub fn configured_expected_clients(&self) -> Option<usize> {
237 self.expected_clients
238 }
239
240 pub fn validate_client_inputs(&self) -> Result<()> {
241 if let Some(expected_clients) = self.expected_clients {
242 if expected_clients == 0 {
243 return Err(Error::Configuration(
244 "expected_clients must be greater than 0".to_owned(),
245 ));
246 }
247 self.program.validate_expected_clients(expected_clients)?;
248 }
249 self.program
250 .validate_owned_client_inputs(&self.client_inputs)
251 }
252
253 pub fn with_inputs<V>(mut self, inputs: &[(&str, V)]) -> Self
255 where
256 V: Clone + Into<Value>,
257 {
258 self.inputs = inputs
259 .iter()
260 .map(|(name, value)| ((*name).to_owned(), value.clone().into()))
261 .collect();
262 self
263 }
264
265 pub fn with_input<V>(mut self, name: impl Into<String>, value: V) -> Self
267 where
268 V: Into<Value>,
269 {
270 self.inputs.push((name.into(), value.into()));
271 self
272 }
273
274 pub fn with_input_file(mut self, path: impl AsRef<Path>) -> Result<Self> {
276 self.inputs = crate::input_file::load_named_inputs_file(path)?;
277 Ok(self)
278 }
279
280 pub fn with_client_input<V>(mut self, client_slot: u64, values: &[V]) -> Self
282 where
283 V: Clone + Into<Value>,
284 {
285 self.client_inputs.push((
286 client_slot,
287 values.iter().cloned().map(Into::into).collect(),
288 ));
289 self
290 }
291
292 pub fn with_client_input_file(mut self, path: impl AsRef<Path>) -> Result<Self> {
294 self.client_inputs = crate::input_file::load_client_inputs_file(path)?;
295 Ok(self)
296 }
297
298 pub fn with_client_inputs<V>(mut self, inputs: &[(u64, &[V])]) -> Self
300 where
301 V: Clone + Into<Value>,
302 {
303 self.client_inputs = inputs
304 .iter()
305 .map(|(client_slot, values)| {
306 (
307 *client_slot,
308 values.iter().cloned().map(Into::into).collect(),
309 )
310 })
311 .collect();
312 self
313 }
314
315 pub fn expected_output_clients(mut self, n: usize) -> Self {
317 self.expected_clients = Some(n);
318 self
319 }
320
321 pub fn local_runner_path(mut self, path: impl AsRef<Path>) -> Self {
323 self.local_runner_path = Some(path.as_ref().to_path_buf());
324 self
325 }
326
327 pub fn local_network(&self) -> LocalNetworkBuilder<'_> {
333 LocalNetworkBuilder::new(self)
334 }
335
336 pub(crate) fn input_values_for_function(&self, function_name: &str) -> Result<Vec<Value>> {
337 let function = self
338 .program
339 .function(function_name)
340 .ok_or_else(|| Error::FunctionNotFound(function_name.to_owned()))?;
341 function.validate_inputs(&self.inputs)?;
342 let parameters = function.parameters();
343 let mut values = Vec::with_capacity(parameters.len());
344
345 for parameter in parameters {
346 let value = self
347 .inputs
348 .iter()
349 .find(|(name, _)| name == parameter)
350 .map(|(_, value)| value)
351 .expect("function input validation should guarantee one value per parameter");
352 values.push(value.clone());
353 }
354
355 Ok(values)
356 }
357
358 pub fn execute_clear(&self) -> Result<Vec<Value>> {
359 self.execute_clear_function("main")
360 }
361
362 pub fn execute_clear_function(&self, name: &str) -> Result<Vec<Value>> {
363 vm::execute_clear(self, name)
364 }
365
366 pub fn execute<A>(&self, name: &str, args: A) -> Result<Vec<Value>>
372 where
373 A: ProgramArgs,
374 {
375 let args = args.into_values();
376 vm::execute_clear_with_sdk_args(self, name, &args)
377 }
378
379 pub fn execute_main<A>(&self, args: A) -> Result<Vec<Value>>
381 where
382 A: ProgramArgs,
383 {
384 self.execute("main", args)
385 }
386
387 pub async fn execute_local(&self) -> Result<Vec<Value>> {
393 self.execute_local_function("main").await
394 }
395
396 pub async fn execute_local_function(&self, name: &str) -> Result<Vec<Value>> {
398 vm::execute_local(self, name).await
399 }
400}
401
402#[derive(Debug, Clone)]
403pub struct LocalNetworkBuilder<'a> {
404 runtime: &'a StoffelRuntime,
405 entry: String,
406 runner_path: Option<PathBuf>,
407 timeout: Option<Duration>,
408}
409
410impl<'a> LocalNetworkBuilder<'a> {
411 fn new(runtime: &'a StoffelRuntime) -> Self {
412 Self {
413 runtime,
414 entry: "main".to_owned(),
415 runner_path: None,
416 timeout: None,
417 }
418 }
419
420 pub fn entry(mut self, entry: impl Into<String>) -> Self {
422 self.entry = entry.into();
423 self
424 }
425
426 pub fn runner_path(mut self, path: impl AsRef<Path>) -> Self {
428 self.runner_path = Some(path.as_ref().to_path_buf());
429 self
430 }
431
432 pub fn timeout(mut self, timeout: Duration) -> Self {
434 self.timeout = Some(timeout);
435 self
436 }
437
438 pub fn configured_entry(&self) -> &str {
439 &self.entry
440 }
441
442 pub fn configured_runner_path(&self) -> Option<&Path> {
443 self.runner_path.as_deref()
444 }
445
446 pub fn configured_timeout(&self) -> Option<Duration> {
447 self.timeout
448 }
449
450 pub async fn run(self) -> Result<Vec<Value>> {
452 if matches!(self.timeout, Some(timeout) if timeout.is_zero()) {
453 return Err(Error::Configuration(
454 "local network timeout must be greater than zero".to_owned(),
455 ));
456 }
457 vm::execute_local_with_options(
458 self.runtime,
459 &self.entry,
460 vm::LocalExecutionOptions {
461 runner_path: self.runner_path,
462 timeout: self.timeout,
463 },
464 )
465 .await
466 }
467}