1use std::collections::{BTreeMap, BTreeSet};
2
3use alloy_primitives::FixedBytes;
4use alloy_primitives::{hex::ToHexExt, Address, U256};
5use alloy_rpc_types_eth::{Account as AlloyAccount, Header as ExecutionHeader};
6
7use bankai_types::api::proofs::HashingFunctionDto;
8use bankai_types::api::proofs::{HeaderRequestDto, LightClientProofRequestDto};
9use bankai_types::fetch::evm::execution::{StorageSlotProof, TxProof};
10use bankai_types::fetch::evm::TxProofRequest;
11use bankai_types::fetch::evm::{
12 beacon::BeaconHeaderProof,
13 execution::{AccountProof, ExecutionHeaderProof},
14 AccountProofRequest, BeaconHeaderProofRequest, EvmProofs, EvmProofsRequest,
15 ExecutionHeaderProofRequest, StorageSlotProofRequest,
16};
17use bankai_types::fetch::ProofWrapper;
18use bankai_types::verify::evm::beacon::BeaconHeader;
19use tree_hash::TreeHash;
20
21use crate::errors::{SdkError, SdkResult};
22use crate::fetch::bankai::stwo::parse_block_proof_value;
23use crate::fetch::clients::bankai_api::ApiClient;
24use crate::fetch::evm::execution::ExecutionChainFetcher;
25use crate::{Bankai, Network};
26
27pub struct ProofBatchBuilder<'a> {
28 bankai: &'a Bankai,
29 network: Network,
30 bankai_block_number: u64,
31 hashing: HashingFunctionDto,
32 evm: EvmProofsRequest,
33}
34
35impl<'a> ProofBatchBuilder<'a> {
36 pub fn new(
37 bankai: &'a Bankai,
38 network: Network,
39 bankai_block_number: u64,
40 hashing: HashingFunctionDto,
41 ) -> Self {
42 Self {
43 bankai,
44 network,
45 bankai_block_number,
46 hashing,
47 evm: EvmProofsRequest {
48 execution_header: None,
49 beacon_header: None,
50 account: None,
51 storage_slot: None,
52 tx_proof: None,
53 },
54 }
55 }
56
57 pub fn evm_execution_header(mut self, block_number: u64) -> Self {
63 let network_id = self.network.execution_network_id();
64 let mut v = self.evm.execution_header.take().unwrap_or_default();
65 v.push(ExecutionHeaderProofRequest {
66 network_id,
67 block_number,
68 hashing_function: self.hashing,
69 bankai_block_number: self.bankai_block_number,
70 });
71 self.evm.execution_header = Some(v);
72 self
73 }
74
75 pub fn evm_beacon_header(mut self, slot: u64) -> Self {
81 let network_id = self.network.beacon_network_id();
82 let mut v = self.evm.beacon_header.take().unwrap_or_default();
83 v.push(BeaconHeaderProofRequest {
84 network_id,
85 slot,
86 hashing_function: self.hashing,
87 bankai_block_number: self.bankai_block_number,
88 });
89 self.evm.beacon_header = Some(v);
90 self
91 }
92
93 pub fn evm_account(mut self, block_number: u64, address: Address) -> Self {
100 let network_id = self.network.execution_network_id();
101 let mut v = self.evm.account.take().unwrap_or_default();
102 v.push(AccountProofRequest {
103 network_id,
104 block_number,
105 address,
106 });
107 self.evm.account = Some(v);
108 self
109 }
110
111 pub fn evm_storage_slot(
119 mut self,
120 block_number: u64,
121 address: Address,
122 slot_keys: Vec<U256>,
123 ) -> Self {
124 let network_id = self.network.execution_network_id();
125 let mut v = self.evm.storage_slot.take().unwrap_or_default();
126 v.push(StorageSlotProofRequest {
127 network_id,
128 block_number,
129 address,
130 slot_keys,
131 });
132 self.evm.storage_slot = Some(v);
133 self
134 }
135
136 pub fn evm_tx(mut self, tx_hash: FixedBytes<32>) -> Self {
142 let network_id = self.network.execution_network_id();
143 let mut v = self.evm.tx_proof.take().unwrap_or_default();
144 v.push(TxProofRequest {
145 network_id,
146 tx_hash,
147 });
148 self.evm.tx_proof = Some(v);
149 self
150 }
151
152 pub async fn execute(self) -> SdkResult<ProofWrapper> {
153 let mut exec_headers: BTreeSet<(u64, u64)> = BTreeSet::new(); let mut beacon_headers: BTreeSet<(u64, u64)> = BTreeSet::new(); if let Some(explicit) = &self.evm.execution_header {
158 for r in explicit {
159 exec_headers.insert((r.network_id, r.block_number));
160 }
161 }
162 if let Some(bh) = &self.evm.beacon_header {
163 for r in bh {
164 beacon_headers.insert((r.network_id, r.slot));
165 }
166 }
167 if let Some(accounts) = &self.evm.account {
168 for r in accounts {
169 exec_headers.insert((r.network_id, r.block_number));
170 }
171 }
172 if let Some(slots) = &self.evm.storage_slot {
173 for r in slots {
174 exec_headers.insert((r.network_id, r.block_number));
175 }
176 }
177
178 let mut tx_proofs: Vec<TxProof> = Vec::new();
179 if let Some(txs) = &self.evm.tx_proof {
180 let exec_fetcher: &ExecutionChainFetcher = self
181 .bankai
182 .evm
183 .execution()
184 .map_err(|_| SdkError::NotConfigured("EVM execution fetcher".into()))?;
185 for req in txs {
186 if exec_fetcher.network_id() != req.network_id {
187 return Err(SdkError::InvalidInput(
188 "execution network_id mismatch".into(),
189 ));
190 }
191 let proof = exec_fetcher.tx_proof(req.tx_hash).await?;
192 tx_proofs.push(proof);
193 }
194 };
195
196 for proof in tx_proofs.clone() {
198 exec_headers.insert((proof.network_id, proof.block_number));
199 }
200
201 let mut exec_header_map: BTreeMap<(u64, u64), ExecutionHeader> = BTreeMap::new();
203 let mut beacon_header_map: BTreeMap<(u64, u64), BeaconHeader> = BTreeMap::new();
204
205 for (network_id, block_number) in &exec_headers {
206 let fetcher = self
207 .bankai
208 .evm
209 .execution()
210 .map_err(|_| SdkError::NotConfigured("EVM execution fetcher".into()))?;
211 if fetcher.network_id() != *network_id {
212 return Err(SdkError::InvalidInput(format!(
213 "execution network_id mismatch: requested {}, configured {}",
214 network_id,
215 fetcher.network_id()
216 )));
217 }
218 let header = fetcher.header_only(*block_number).await?;
219 exec_header_map.insert((*network_id, *block_number), header);
220 }
221
222 for (network_id, slot) in &beacon_headers {
223 let fetcher = self
224 .bankai
225 .evm
226 .beacon()
227 .map_err(|_| SdkError::NotConfigured("EVM beacon fetcher".into()))?;
228 if fetcher.network_id() != *network_id {
229 return Err(SdkError::InvalidInput(format!(
230 "beacon network_id mismatch: requested {}, configured {}",
231 network_id,
232 fetcher.network_id()
233 )));
234 }
235 let header = fetcher.header_only(*slot).await?;
236 beacon_header_map.insert((*network_id, *slot), header);
237 }
238
239 let mut requested_headers: Vec<HeaderRequestDto> = Vec::new();
241 for ((network_id, _), header) in exec_header_map.iter() {
242 requested_headers.push(HeaderRequestDto {
243 network_id: *network_id,
244 header_hash: header.hash.to_string(),
245 });
246 }
247 for ((network_id, _), header) in beacon_header_map.iter() {
248 let root = header.tree_hash_root();
249 requested_headers.push(HeaderRequestDto {
250 network_id: *network_id,
251 header_hash: format!("0x{}", root.encode_hex()),
252 });
253 }
254
255 let api: &ApiClient = &self.bankai.api;
257 let lc_req = LightClientProofRequestDto {
258 bankai_block_number: Some(self.bankai_block_number),
259 hashing_function: self.hashing,
260 requested_headers,
261 };
262 let lc_proof = api.get_light_client_proof(&lc_req).await?;
263
264 let block_proof = parse_block_proof_value(lc_proof.block_proof.proof);
266 let block_proof = block_proof?;
267
268 let mut mmr_by_key: BTreeMap<(u64, String), _> = BTreeMap::new();
270 for p in lc_proof.mmr_proofs {
271 mmr_by_key.insert((p.network_id, p.header_hash.clone()), p);
272 }
273
274 let mut exec_header_proofs: Vec<ExecutionHeaderProof> = Vec::new();
276 for ((network_id, _), header) in exec_header_map.iter() {
277 let key = (*network_id, header.hash.to_string());
278 let mmr = mmr_by_key.get(&key).ok_or_else(|| {
279 SdkError::NotFound("missing MMR proof for execution header".into())
280 })?;
281 exec_header_proofs.push(ExecutionHeaderProof {
282 header: header.clone(),
283 mmr_proof: mmr.clone().into(),
284 });
285 }
286
287 let mut beacon_header_proofs: Vec<BeaconHeaderProof> = Vec::new();
288 for ((network_id, _), header) in beacon_header_map.iter() {
289 let root = header.tree_hash_root();
290 let key = (*network_id, format!("0x{}", root.encode_hex()));
291 let mmr = mmr_by_key
292 .get(&key)
293 .ok_or_else(|| SdkError::NotFound("missing MMR proof for beacon header".into()))?;
294 beacon_header_proofs.push(BeaconHeaderProof {
295 header: header.clone(),
296 mmr_proof: mmr.clone().into(),
297 });
298 }
299
300 let mut account_proofs: Vec<AccountProof> = Vec::new();
302 if let Some(accounts) = &self.evm.account {
303 let exec_fetcher: &ExecutionChainFetcher = self
304 .bankai
305 .evm
306 .execution()
307 .map_err(|_| SdkError::NotConfigured("EVM execution fetcher".into()))?;
308 for req in accounts {
309 if exec_fetcher.network_id() != req.network_id {
310 return Err(SdkError::InvalidInput(
311 "execution network_id mismatch".into(),
312 ));
313 }
314 let proof = exec_fetcher
315 .account(
316 req.block_number,
317 req.address,
318 self.hashing,
319 self.bankai_block_number,
320 )
321 .await?;
322 let header = exec_header_map
323 .get(&(req.network_id, req.block_number))
324 .ok_or_else(|| SdkError::NotFound("header not fetched for account".into()))?;
325 let account_state: AlloyAccount = AlloyAccount {
326 balance: proof.balance,
327 nonce: proof.nonce,
328 code_hash: proof.code_hash,
329 storage_root: proof.storage_hash,
330 };
331 let account_proof = AccountProof {
332 account: account_state,
333 address: req.address,
334 network_id: req.network_id,
335 block_number: req.block_number,
336 state_root: header.state_root,
337 mpt_proof: proof.account_proof,
338 };
339 account_proofs.push(account_proof);
340 }
341 }
342
343 let mut storage_slot_proofs: Vec<StorageSlotProof> = Vec::new();
345 if let Some(slots) = &self.evm.storage_slot {
346 let exec_fetcher: &ExecutionChainFetcher = self
347 .bankai
348 .evm
349 .execution()
350 .map_err(|_| SdkError::NotConfigured("EVM execution fetcher".into()))?;
351 for req in slots {
352 if exec_fetcher.network_id() != req.network_id {
353 return Err(SdkError::InvalidInput(
354 "execution network_id mismatch".into(),
355 ));
356 }
357 let proof = exec_fetcher
358 .storage_slot_proof(
359 req.block_number,
360 req.address,
361 &req.slot_keys,
362 self.hashing,
363 self.bankai_block_number,
364 )
365 .await?;
366 storage_slot_proofs.push(proof);
367 }
368 }
369
370 let evm_proofs = EvmProofs {
371 execution_header_proof: if exec_header_proofs.is_empty() {
372 None
373 } else {
374 Some(exec_header_proofs)
375 },
376 beacon_header_proof: if beacon_header_proofs.is_empty() {
377 None
378 } else {
379 Some(beacon_header_proofs)
380 },
381 account_proof: if account_proofs.is_empty() {
382 None
383 } else {
384 Some(account_proofs)
385 },
386 storage_slot_proof: if storage_slot_proofs.is_empty() {
387 None
388 } else {
389 Some(storage_slot_proofs)
390 },
391 tx_proof: if tx_proofs.is_empty() {
392 None
393 } else {
394 Some(tx_proofs)
395 },
396 };
397
398 let wrapper = ProofWrapper {
399 block_proof,
400 hashing_function: self.hashing,
401 evm_proofs: Some(evm_proofs),
402 };
403 Ok(wrapper)
404 }
405}