boundless_market/request_builder/
requirements_layer.rs1use super::{Adapt, Layer, MissingFieldError, RequestParams};
16#[cfg(feature = "blake3-groth16")]
17use crate::blake3_groth16;
18use crate::contracts::{Callback, Predicate, Requirements};
19#[cfg(feature = "blake3-groth16")]
20use crate::selector::is_blake3_groth16_selector;
21use alloy::primitives::{aliases::U96, Address, FixedBytes, B256};
22use anyhow::{ensure, Context};
23use clap::Args;
24use derive_builder::Builder;
25use risc0_zkvm::{compute_image_id, Journal};
26use risc0_zkvm::{sha::Digestible, Digest};
27
28const DEFAULT_CALLBACK_GAS_LIMT: u64 = 100000u64;
29
30#[non_exhaustive]
35#[derive(Clone, Builder, Default)]
36pub struct RequirementsLayer {}
37
38#[non_exhaustive]
39#[derive(Clone, Debug, Default, Builder, Args)]
40pub struct RequirementParams {
45 #[clap(skip)]
47 #[builder(setter(strip_option, into), default)]
48 pub predicate: Option<Predicate>,
49
50 #[clap(long)]
52 #[builder(setter(strip_option, into), default)]
53 pub image_id: Option<B256>,
54
55 #[clap(long)]
57 #[builder(setter(strip_option, into), default)]
58 pub callback_address: Option<Address>,
59
60 #[clap(long)]
62 #[builder(setter(strip_option), default)]
63 pub callback_gas_limit: Option<u64>,
64
65 #[clap(long)]
67 #[builder(setter(strip_option, into), default)]
68 pub selector: Option<FixedBytes<4>>,
69}
70
71impl TryFrom<Requirements> for RequirementParams {
72 type Error = anyhow::Error;
73
74 fn try_from(value: Requirements) -> Result<Self, Self::Error> {
75 let predicate = Predicate::try_from(value.predicate)?;
76 let image_id = predicate.image_id().map(<[u8; 32]>::from).map(Into::into);
77 Ok(Self {
78 predicate: Some(predicate),
79 selector: Some(value.selector),
80 callback_address: Some(value.callback.addr),
81 callback_gas_limit: Some(value.callback.gasLimit.to()),
82 image_id,
83 })
84 }
85}
86
87impl TryFrom<RequirementParams> for Requirements {
88 type Error = MissingFieldError;
89
90 fn try_from(value: RequirementParams) -> Result<Self, Self::Error> {
91 Ok(Self {
92 predicate: value
93 .predicate
94 .ok_or(MissingFieldError::with_hint(
95 "predicate",
96 "please provide a Predicate with requirements e.g. a digest match on a journal",
97 ))?
98 .into(),
99 selector: value.selector.unwrap_or_default(),
100 callback: Callback {
101 addr: value.callback_address.unwrap_or_default(),
102 gasLimit: U96::from(value.callback_gas_limit.unwrap_or_default()),
103 },
104 })
105 }
106}
107
108impl From<RequirementParamsBuilder> for RequirementParams {
109 fn from(value: RequirementParamsBuilder) -> Self {
110 value.build().expect("implementation error in RequirementParams")
112 }
113}
114
115impl From<&mut RequirementParamsBuilder> for RequirementParams {
117 fn from(value: &mut RequirementParamsBuilder) -> Self {
118 value.clone().into()
119 }
120}
121
122impl RequirementParams {
123 pub fn builder() -> RequirementParamsBuilder {
127 Default::default()
128 }
129}
130
131impl RequirementsLayer {
132 pub fn builder() -> RequirementsLayerBuilder {
136 Default::default()
137 }
138}
139
140impl Layer<(&[u8], &Journal, &RequirementParams)> for RequirementsLayer {
141 type Output = Requirements;
142 type Error = anyhow::Error;
143
144 async fn process(
145 &self,
146 (program, journal, params): (&[u8], &Journal, &RequirementParams),
147 ) -> Result<Self::Output, Self::Error> {
148 let image_id =
149 compute_image_id(program).context("failed to compute image ID for program")?;
150 self.process((image_id, journal, params)).await
151 }
152}
153
154impl Layer<(Digest, &Journal, &RequirementParams)> for RequirementsLayer {
155 type Output = Requirements;
156 type Error = anyhow::Error;
157
158 async fn process(
159 &self,
160 (image_id, journal, params): (Digest, &Journal, &RequirementParams),
161 ) -> Result<Self::Output, Self::Error> {
162 #[allow(unused_mut)]
163 let mut predicate = params.predicate.clone();
164 #[cfg(feature = "blake3-groth16")]
165 if let Some(selector) = ¶ms.selector {
166 if is_blake3_groth16_selector(*selector) {
167 if journal.bytes.len() != 32 {
168 anyhow::bail!(
169 "Blake3Groth16 proofs require a 32-byte journal, got {} bytes",
170 journal.bytes.len()
171 );
172 }
173 if let Some(pred) = &predicate {
174 matches!(pred, Predicate::ClaimDigestMatch(_)).then_some(()).ok_or_else(
175 || {
176 anyhow::anyhow!(
177 "Blake3Groth16 proofs require a ClaimDigestMatch predicate"
178 )
179 },
180 )?;
181 } else {
182 predicate = Some(params.predicate.clone().unwrap_or_else(|| {
183 let blake3_claim_digest = blake3_groth16::Blake3Groth16ReceiptClaim::ok(
184 image_id,
185 journal.bytes.clone(),
186 )
187 .digest();
188 Predicate::claim_digest_match(blake3_claim_digest)
189 }));
190 }
191 }
192 }
193
194 let predicate =
195 predicate.unwrap_or_else(|| Predicate::digest_match(image_id, journal.digest()));
196
197 if let Some(params_image_id) = params.image_id {
198 ensure!(
199 image_id == Digest::from(<[u8; 32]>::from(params_image_id)),
200 "mismatch between specified and computed image ID"
201 )
202 }
203 let callback = params
204 .callback_address
205 .map(|addr| Callback {
206 addr,
207 gasLimit: U96::from(params.callback_gas_limit.unwrap_or(DEFAULT_CALLBACK_GAS_LIMT)),
208 })
209 .unwrap_or_default();
210
211 let selector = params.selector.unwrap_or_default();
212 Ok(Requirements { predicate: predicate.into(), callback, selector })
213 }
214}
215
216impl Adapt<RequirementsLayer> for RequestParams {
217 type Output = RequestParams;
218 type Error = anyhow::Error;
219
220 async fn process_with(self, layer: &RequirementsLayer) -> Result<Self::Output, Self::Error> {
221 tracing::trace!("Processing {self:?} with RequirementsLayer");
222
223 if self.requirements.predicate.is_some() && self.requirements.image_id.is_some() {
226 return Ok(self);
227 }
228
229 let journal = self.require_journal().context("failed to build Requirements for request")?;
230 let requirements = if let Some(image_id) = self.image_id {
231 layer.process((image_id, journal, &self.requirements)).await?
232 } else {
233 let program = self.require_program()?;
234 layer.process((program, journal, &self.requirements)).await?
235 };
236
237 Ok(self.with_requirements(RequirementParams::try_from(requirements)?))
238 }
239}