boundless_market/request_builder/
preflight_layer.rs1use super::{Adapt, RequestParams};
16use crate::{
17 contracts::{RequestInput, RequestInputType},
18 input::GuestEnv,
19 prover_utils::local_executor::LocalExecutor,
20 storage::StorageDownloader,
21 NotProvided,
22};
23use anyhow::{bail, ensure, Context};
24
25#[non_exhaustive]
38#[derive(Clone)]
39pub struct PreflightLayer<D> {
40 executor: LocalExecutor,
41 downloader: Option<D>,
43}
44
45impl<D> Default for PreflightLayer<D> {
46 fn default() -> Self {
47 Self { executor: LocalExecutor::default(), downloader: None }
48 }
49}
50
51impl<D> PreflightLayer<D>
52where
53 D: StorageDownloader,
54{
55 pub fn new(executor: LocalExecutor, downloader: Option<D>) -> Self {
57 Self { executor, downloader }
58 }
59
60 pub fn executor_cloned(&self) -> LocalExecutor {
62 self.executor.clone()
63 }
64
65 async fn fetch_env(&self, input: &RequestInput) -> anyhow::Result<GuestEnv> {
66 let env = match input.inputType {
67 RequestInputType::Inline => GuestEnv::decode(&input.data)?,
68 RequestInputType::Url => {
69 let downloader = self
70 .downloader
71 .as_ref()
72 .context("cannot preflight URL input without downloader")?;
73 let input_url =
74 std::str::from_utf8(&input.data).context("Input URL is not valid UTF-8")?;
75 tracing::info!("Fetching input from {}", input_url);
76 GuestEnv::decode(&downloader.download(input_url).await?)?
77 }
78 _ => bail!("Unsupported input type"),
79 };
80 Ok(env)
81 }
82
83 async fn ensure_image_id(&self, params: RequestParams) -> anyhow::Result<RequestParams> {
85 if params.image_id.is_some() {
86 return Ok(params);
87 }
88 let program = match params.require_program() {
89 Ok(bytes) => bytes.to_vec(),
90 Err(_) => {
91 let url = params.require_program_url()?;
92 let downloader = self
93 .downloader
94 .as_ref()
95 .context("cannot fetch program URL without downloader")?;
96 downloader.download(url.as_str()).await?
97 }
98 };
99 let image_id = risc0_zkvm::compute_image_id(&program)?;
100 Ok(params.with_image_id(image_id))
101 }
102
103 async fn fill_executor_cache_if_ready(&self, params: &RequestParams) {
105 let (Some(image_id), Some(request_input), Some(cycles), Some(journal)) = (
106 params.image_id,
107 params.request_input.as_ref(),
108 params.cycles,
109 params.journal.as_ref(),
110 ) else {
111 return;
112 };
113 let Ok(env) = self.fetch_env(request_input).await else {
114 return;
115 };
116 tracing::debug!("Filling executor cache for {image_id} with {cycles} cycles");
117 self.executor
118 .insert_execution_data(&image_id.to_string(), &env.stdin, cycles, journal.bytes.clone())
119 .await;
120 }
121}
122
123impl<D> Adapt<PreflightLayer<D>> for RequestParams
124where
125 D: StorageDownloader,
126{
127 type Output = RequestParams;
128 type Error = anyhow::Error;
129
130 async fn process_with(
131 mut self,
132 layer: &PreflightLayer<D>,
133 ) -> Result<Self::Output, Self::Error> {
134 if self.cycles.is_some() && self.journal.is_some() {
135 self = layer.ensure_image_id(self).await?;
136 layer.fill_executor_cache_if_ready(&self).await;
137 return Ok(self);
138 }
139
140 tracing::trace!("Processing {self:?} with PreflightLayer");
141
142 let program_url = self.require_program_url().context("failed to preflight request")?;
143 let request_input = self.require_request_input().context("failed to preflight request")?;
144
145 let downloader =
147 layer.downloader.as_ref().context("cannot preflight URL request without downloader")?;
148 let program = downloader.download(program_url.as_str()).await?;
149 let env = layer.fetch_env(request_input).await?;
150 let input_bytes = env.stdin;
152
153 let image_id = risc0_zkvm::compute_image_id(&program)?;
155 let image_id_str = image_id.to_string();
156
157 let (stats, journal) = layer
159 .executor
160 .execute_program(&image_id_str, &program, &input_bytes)
161 .await
162 .map_err(|e| anyhow::anyhow!("preflight execution failed: {}", e))?;
163
164 let cycles = stats.total_cycles;
165 let journal = risc0_zkvm::Journal::new(journal);
166
167 if let Some(provided_image_id) = self.image_id {
169 ensure!(
170 provided_image_id == image_id,
171 "provided image ID does not match computed value: {provided_image_id} != {image_id}"
172 );
173 }
174
175 Ok(self.with_cycles(cycles).with_journal(journal).with_image_id(image_id))
176 }
177}
178
179impl Adapt<PreflightLayer<NotProvided>> for RequestParams {
180 type Output = RequestParams;
181 type Error = anyhow::Error;
182
183 async fn process_with(
184 self,
185 _: &PreflightLayer<NotProvided>,
186 ) -> Result<Self::Output, Self::Error> {
187 if self.cycles.is_some() && self.journal.is_some() {
188 return Ok(self);
189 }
190
191 bail!("cannot preflight program without downloader")
192 }
193}