1use crate::{
16 history::beacon_roots::BeaconRootsContract, state::WrapStateDb, Commitment, EvmBlockHeader,
17 EvmFactory, GuestEvmEnv,
18};
19use alloy_primitives::{B256, U256};
20use anyhow::{ensure, Context};
21
22pub struct SteelVerifier<E> {
62 env: E,
63}
64
65impl<'a, F: EvmFactory> SteelVerifier<&'a GuestEvmEnv<F>> {
66 pub fn new(env: &'a GuestEvmEnv<F>) -> Self {
68 Self { env }
69 }
70
71 #[inline]
76 pub fn verify(&self, commitment: &Commitment) {
77 self.verify_with_config_id(commitment, self.env.commit.configID);
78 }
79
80 pub fn verify_with_config_id(&self, commitment: &Commitment, config_id: B256) {
83 assert_eq!(commitment.configID, config_id, "Invalid config ID");
84 let (id, version) = commitment.decode_id();
85 match version {
86 0 => {
87 let block_number =
88 validate_block_number(self.env.header().inner(), id).expect("Invalid ID");
89 let block_hash = self.env.db().block_hash(block_number);
90 assert_eq!(block_hash, commitment.digest, "Invalid digest");
91 }
92 1 => {
93 let db = WrapStateDb::new(self.env.db(), self.env.header());
94 let beacon_root = BeaconRootsContract::get_from_db(db, id)
95 .expect("calling BeaconRootsContract failed");
96 assert_eq!(beacon_root, commitment.digest, "Invalid digest");
97 }
98 v => unimplemented!("Invalid commitment version {}", v),
99 }
100 }
101}
102
103#[cfg(feature = "host")]
104mod host {
105 use super::*;
106 use crate::{history::beacon_roots, host::HostEvmEnv};
107 use anyhow::Context;
108 use revm::Database;
109
110 impl<'a, D, F: EvmFactory, C> SteelVerifier<&'a mut HostEvmEnv<D, F, C>>
111 where
112 D: crate::EvmDatabase + Send + 'static,
113 beacon_roots::Error: From<<D as Database>::Error>,
114 anyhow::Error: From<<D as Database>::Error>,
115 <D as Database>::Error: Send + 'static,
116 {
117 pub fn preflight(env: &'a mut HostEvmEnv<D, F, C>) -> Self {
125 Self { env }
126 }
127
128 #[inline]
133 pub async fn verify(self, commitment: &Commitment) -> anyhow::Result<()> {
134 let config_id = self.env.commit.config_id();
135 self.verify_with_config_id(commitment, config_id).await
136 }
137
138 pub async fn verify_with_config_id(
141 self,
142 commitment: &Commitment,
143 config_id: B256,
144 ) -> anyhow::Result<()> {
145 log::info!("Executing preflight verifying {commitment:?}");
146
147 ensure!(commitment.configID == config_id, "invalid config ID");
148 let (id, version) = commitment.decode_id();
149 match version {
150 0 => {
151 let block_number = validate_block_number(self.env.header().inner(), id)
152 .context("invalid ID")?;
153 let block_hash = self
154 .env
155 .spawn_with_db(move |db| db.block_hash(block_number))
156 .await?;
157 ensure!(block_hash == commitment.digest, "invalid digest");
158
159 Ok(())
160 }
161 1 => {
162 let beacon_root = self
163 .env
164 .spawn_with_db(move |db| BeaconRootsContract::get_from_db(db, id))
165 .await
166 .with_context(|| format!("calling BeaconRootsContract({id}) failed"))?;
167 ensure!(beacon_root == commitment.digest, "invalid digest");
168
169 Ok(())
170 }
171 v => unimplemented!("Invalid commitment version {}", v),
172 }
173 }
174 }
175}
176
177fn validate_block_number(header: &impl EvmBlockHeader, block_number: U256) -> anyhow::Result<u64> {
178 let block_number = block_number.try_into().context("invalid block number")?;
179 let diff = header.number().saturating_sub(block_number);
181 ensure!(
182 diff > 0 && diff <= 256,
183 "valid range is the last 256 blocks (not including the current one)"
184 );
185 Ok(block_number)
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::{
192 ethereum::{EthEvmEnv, ETH_MAINNET_CHAIN_SPEC},
193 test_utils::get_el_url,
194 CommitmentVersion,
195 };
196 use alloy::{
197 consensus::BlockHeader,
198 network::{primitives::HeaderResponse, BlockResponse},
199 providers::{Provider, ProviderBuilder},
200 rpc::types::BlockNumberOrTag as AlloyBlockNumberOrTag,
201 };
202 use test_log::test;
203
204 #[test(tokio::test)]
205 #[cfg_attr(
206 any(not(feature = "rpc-tests"), no_auth),
207 ignore = "RPC tests are disabled"
208 )]
209 async fn verify_block_commitment() {
210 let el = ProviderBuilder::new().connect_http(get_el_url());
211
212 let latest = el.get_block_number().await.unwrap();
214 let block = el
215 .get_block_by_number((latest - 1).into())
216 .await
217 .expect("eth_getBlockByNumber failed")
218 .unwrap();
219 let header = block.header();
220 let commit = Commitment::new(
221 CommitmentVersion::Block as u16,
222 header.number(),
223 header.hash(),
224 ETH_MAINNET_CHAIN_SPEC.digest(),
225 );
226
227 let mut env = EthEvmEnv::builder()
229 .provider(el)
230 .chain_spec(Ð_MAINNET_CHAIN_SPEC)
231 .build()
232 .await
233 .unwrap();
234 SteelVerifier::preflight(&mut env)
235 .verify(&commit)
236 .await
237 .unwrap();
238
239 let env = env
241 .into_input()
242 .await
243 .unwrap()
244 .into_env(Ð_MAINNET_CHAIN_SPEC);
245 SteelVerifier::new(&env).verify(&commit);
246 }
247
248 #[test(tokio::test)]
249 #[cfg_attr(
250 any(not(feature = "rpc-tests"), no_auth),
251 ignore = "RPC tests are disabled"
252 )]
253 async fn verify_beacon_commitment() {
254 let el = ProviderBuilder::new().connect_http(get_el_url());
255
256 let block = el
258 .get_block_by_number(AlloyBlockNumberOrTag::Latest)
259 .await
260 .expect("eth_getBlockByNumber failed")
261 .unwrap();
262 let header = block.header();
263 let commit = Commitment::new(
264 CommitmentVersion::Beacon as u16,
265 header.timestamp,
266 header.parent_beacon_block_root.unwrap(),
267 ETH_MAINNET_CHAIN_SPEC.digest(),
268 );
269
270 let mut env = EthEvmEnv::builder()
272 .provider(el)
273 .chain_spec(Ð_MAINNET_CHAIN_SPEC)
274 .build()
275 .await
276 .unwrap();
277 SteelVerifier::preflight(&mut env)
278 .verify(&commit)
279 .await
280 .unwrap();
281
282 let env = env
284 .into_input()
285 .await
286 .unwrap()
287 .into_env(Ð_MAINNET_CHAIN_SPEC);
288 SteelVerifier::new(&env).verify(&commit);
289 }
290}