risc0_ethereum_contracts/
set_verifier.rs1use core::time::Duration;
16
17use crate::{
18 event_query::EventQueryConfig,
19 receipt::{decode_seal, decode_seal_with_claim},
20 IRiscZeroSetVerifier::{self, IRiscZeroSetVerifierErrors, IRiscZeroSetVerifierInstance},
21 IRiscZeroVerifier,
22};
23use alloy::{
24 network::Ethereum,
25 primitives::{Address, Bytes, B256},
26 providers::Provider,
27};
28use anyhow::{bail, Context, Result};
29use risc0_aggregation::{merkle_path_root, GuestState, MerkleMountainRange, SetInclusionReceipt};
30use risc0_zkvm::{
31 sha::{Digest, Digestible},
32 ReceiptClaim,
33};
34
35const TXN_CONFIRM_TIMEOUT: Duration = Duration::from_secs(45);
36
37#[derive(Clone)]
38pub struct SetVerifierService<P> {
39 instance: IRiscZeroSetVerifierInstance<P, Ethereum>,
40 caller: Address,
41 tx_timeout: Duration,
42 event_query_config: EventQueryConfig,
43}
44
45impl<P> SetVerifierService<P>
46where
47 P: Provider<Ethereum> + 'static + Clone,
48{
49 pub fn new(address: Address, provider: P, caller: Address) -> Self {
50 let instance = IRiscZeroSetVerifier::new(address, provider);
51
52 Self {
53 instance,
54 caller,
55 tx_timeout: TXN_CONFIRM_TIMEOUT,
56 event_query_config: EventQueryConfig::default(),
57 }
58 }
59
60 pub fn instance(&self) -> &IRiscZeroSetVerifierInstance<P, Ethereum> {
61 &self.instance
62 }
63
64 pub fn with_timeout(self, tx_timeout: Duration) -> Self {
65 Self { tx_timeout, ..self }
66 }
67
68 pub fn with_event_query_config(self, config: EventQueryConfig) -> Self {
70 Self {
71 event_query_config: config,
72 ..self
73 }
74 }
75
76 pub async fn contains_root(&self, root: B256) -> Result<bool> {
77 tracing::debug!("Calling containsRoot({:?})", root);
78 let call = self.instance.containsRoot(root);
79
80 call.call().await.context("call failed")
81 }
82
83 pub async fn submit_merkle_root(&self, root: B256, seal: Bytes) -> Result<()> {
84 tracing::debug!("Calling submitMerkleRoot({:?},{:?})", root, seal);
85 let call = self.instance.submitMerkleRoot(root, seal).from(self.caller);
86 let pending_tx = call
87 .send()
88 .await
89 .map_err(IRiscZeroSetVerifierErrors::decode_error)?;
90 tracing::debug!("Broadcasting tx {}", pending_tx.tx_hash());
91 let tx_hash = pending_tx
92 .with_timeout(Some(self.tx_timeout))
93 .watch()
94 .await
95 .context("failed to confirm tx")?;
96
97 tracing::info!("Submitted Merkle root {}: {}", root, tx_hash);
98
99 Ok(())
100 }
101
102 pub async fn verify(&self, seal: Bytes, image_id: B256, journal_digest: B256) -> Result<()> {
103 tracing::debug!(
104 "Calling verify({:?},{:?},{:?})",
105 seal,
106 image_id,
107 journal_digest
108 );
109 let verifier = IRiscZeroVerifier::new(
110 *self.instance().address(),
111 self.instance().provider().clone(),
112 );
113 verifier
114 .verify(seal, image_id, journal_digest)
115 .call()
116 .await
117 .map_err(|_| anyhow::anyhow!("Verification failed"))?;
118
119 Ok(())
120 }
121
122 pub async fn image_info(&self) -> Result<(B256, String)> {
123 tracing::debug!("Calling imageInfo()");
124 let (image_id, image_url) = self
125 .instance
126 .imageInfo()
127 .call()
128 .await
129 .context("call failed")?
130 .into();
131
132 Ok((image_id, image_url))
133 }
134
135 pub async fn fetch_verified_root_seal(&self, root: B256) -> Result<Bytes> {
137 self.query_verified_root_event(root, None, None).await
138 }
139
140 async fn get_latest_block(&self) -> Result<u64> {
141 self.instance
142 .provider()
143 .get_block_number()
144 .await
145 .context("Failed to get latest block number")
146 }
147
148 async fn query_verified_root_event(
157 &self,
158 root: B256,
159 lower_bound: Option<u64>,
160 upper_bound: Option<u64>,
161 ) -> Result<Bytes> {
162 let mut upper_block = if let Some(upper_bound) = upper_bound {
163 upper_bound
164 } else {
165 self.get_latest_block().await?
166 };
167 let start_block = lower_bound.unwrap_or_else(|| {
168 upper_block.saturating_sub(
169 self.event_query_config.block_range * self.event_query_config.max_iterations,
170 )
171 });
172
173 for _ in 0..self.event_query_config.max_iterations {
175 if upper_block < start_block {
177 break;
178 }
179
180 let lower_block = upper_block.saturating_sub(self.event_query_config.block_range);
182
183 let mut event_filter = self.instance.VerifiedRoot_filter();
185 event_filter.filter = event_filter
186 .filter
187 .topic1(root)
188 .from_block(lower_block)
189 .to_block(upper_block);
190
191 let logs = event_filter.query().await?;
193
194 if let Some((verified_root, _)) = logs.first() {
196 let seal = verified_root.seal.clone();
197 return Ok(seal);
198 }
199 upper_block = lower_block.saturating_sub(1);
201 }
202
203 bail!("VerifiedRoot event not found for root {:?}", root);
205 }
206
207 pub async fn fetch_receipt(
210 &self,
211 seal: Bytes,
212 image_id: impl Into<Digest>,
213 journal: impl Into<Vec<u8>>,
214 ) -> Result<SetInclusionReceipt<ReceiptClaim>> {
215 let journal = journal.into();
216 let claim = ReceiptClaim::ok(image_id, journal.clone());
217 self.fetch_receipt_with_claim(seal, claim, journal).await
218 }
219
220 pub async fn fetch_receipt_with_claim(
223 &self,
224 seal: Bytes,
225 claim: ReceiptClaim,
226 journal: impl Into<Vec<u8>>,
227 ) -> Result<SetInclusionReceipt<ReceiptClaim>> {
228 let receipt = decode_seal_with_claim(seal, claim.clone(), journal)
229 .map_err(|e| anyhow::anyhow!("Failed to decode seal: {:?}", e))?;
230
231 let set_inclusion_receipt = receipt
232 .set_inclusion_receipt()
233 .ok_or_else(|| anyhow::anyhow!("Seal is not a SetInclusionReceipt"))?;
234
235 let root = merkle_path_root(claim.digest(), &set_inclusion_receipt.merkle_path);
236 let root_seal = self
237 .fetch_verified_root_seal(<[u8; 32]>::from(root).into())
238 .await?;
239
240 let set_builder_id = Digest::from_bytes(self.image_info().await?.0 .0);
241 let state = GuestState {
242 self_image_id: set_builder_id,
243 mmr: MerkleMountainRange::new_finalized(root),
244 };
245 let aggregation_set_journal = state.encode();
246
247 let root_receipt = decode_seal(root_seal, set_builder_id, aggregation_set_journal)?
248 .receipt()
249 .cloned()
250 .ok_or_else(|| anyhow::anyhow!("Failed to decode root seal"))?;
251
252 let receipt = set_inclusion_receipt.clone().with_root(root_receipt);
253
254 Ok(receipt)
255 }
256}