boundless_market/request_builder/
finalizer.rs1use super::{Adapt, Layer, RequestParams};
16use crate::{
17 contracts::{
18 FulfillmentData, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput,
19 Requirements,
20 },
21 selector::is_blake3_groth16_selector,
22 util::now_timestamp,
23};
24use anyhow::{bail, Context};
25use derive_builder::Builder;
26use url::Url;
27
28#[non_exhaustive]
29#[derive(Debug, Clone, Builder)]
30pub struct FinalizerConfig {
34 #[builder(default = true)]
36 pub check_expiration: bool,
37}
38
39#[non_exhaustive]
44#[derive(Debug, Clone, Default)]
45pub struct Finalizer {
46 pub config: FinalizerConfig,
48}
49
50impl From<FinalizerConfig> for Finalizer {
51 fn from(config: FinalizerConfig) -> Self {
52 Self { config }
53 }
54}
55
56impl Default for FinalizerConfig {
57 fn default() -> Self {
58 Self::builder().build().expect("implementation error in Default for FinalizerConfig")
59 }
60}
61
62impl FinalizerConfig {
63 pub fn builder() -> FinalizerConfigBuilder {
67 Default::default()
68 }
69}
70
71impl Layer<(Url, RequestInput, Requirements, Offer, RequestId)> for Finalizer {
72 type Output = ProofRequest;
73 type Error = anyhow::Error;
74
75 async fn process(
76 &self,
77 (program_url, input, requirements, offer, request_id): (
78 Url,
79 RequestInput,
80 Requirements,
81 Offer,
82 RequestId,
83 ),
84 ) -> Result<Self::Output, Self::Error> {
85 let request = ProofRequest {
86 requirements,
87 id: request_id.into(),
88 imageUrl: program_url.into(),
89 input,
90 offer,
91 };
92
93 request.validate().context("built request is invalid; check request parameters")?;
94 if self.config.check_expiration && request.is_expired() {
95 bail!(
96 "request expired at {}; current time is {}",
97 request.expires_at(),
98 now_timestamp()
99 );
100 }
101 if self.config.check_expiration && request.is_lock_expired() {
102 bail!(
103 "request lock expired at {}; current time is {}",
104 request.lock_expires_at(),
105 now_timestamp()
106 );
107 }
108 Ok(request)
109 }
110}
111
112impl Adapt<Finalizer> for RequestParams {
113 type Output = ProofRequest;
114 type Error = anyhow::Error;
115
116 async fn process_with(self, layer: &Finalizer) -> Result<Self::Output, Self::Error> {
117 tracing::trace!("Processing {self:?} with Finalizer");
118
119 let program_url = self.require_program_url().context("failed to build request")?.clone();
121 let input = self.require_request_input().context("failed to build request")?.clone();
122 let requirements: Requirements = self
123 .requirements
124 .clone()
125 .try_into()
126 .context("failed to build request: requirements are incomplete")?;
127 let offer: Offer = self
128 .offer
129 .clone()
130 .try_into()
131 .context("failed to build request: offer is incomplete")?;
132 let request_id = self.require_request_id().context("failed to build request")?.clone();
133
134 let predicate = Predicate::try_from(requirements.predicate.clone())?;
136 let eval = match (&self.journal, self.image_id) {
137 (Some(journal), Some(image_id)) => {
138 tracing::debug!("Evaluating journal and image id against predicate ");
139 let eval_data = if is_blake3_groth16_selector(requirements.selector) {
140 if requirements.predicate.predicateType != PredicateType::ClaimDigestMatch {
141 bail!("Blake3Groth16 proofs require a ClaimDigestMatch predicate");
142 }
143 if journal.bytes.len() != 32 {
144 bail!(
145 "Blake3Groth16 proofs require a 32-byte journal, got {} bytes",
146 journal.bytes.len()
147 );
148 }
149 FulfillmentData::None
151 } else {
152 FulfillmentData::from_image_id_and_journal(image_id, journal.bytes.clone())
153 };
154 predicate.eval(&eval_data).is_some()
155 }
156 _ => true,
158 };
159 if !eval {
160 bail!("journal in request builder does not match requirements predicate; check request parameters.\npredicate = {:?}\njournal = {:?}", predicate, self.journal.as_ref().map(hex::encode));
161 }
162
163 layer.process((program_url, input, requirements, offer, request_id)).await
164 }
165}