bankai_sdk/fetch/
batch.rs

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    /// Adds an execution header to the batch
58    ///
59    /// # Arguments
60    ///
61    /// * `block_number` - The execution layer block number to fetch
62    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    /// Adds a beacon header to the batch
76    ///
77    /// # Arguments
78    ///
79    /// * `slot` - The beacon chain slot number to fetch
80    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    /// Adds an account proof to the batch
94    ///
95    /// # Arguments
96    ///
97    /// * `block_number` - The execution layer block number to query
98    /// * `address` - The account address to fetch proof for
99    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    /// Adds a storage slot proof to the batch for one or more slots from the same contract.
112    ///
113    /// # Arguments
114    ///
115    /// * `block_number` - The execution layer block number to query
116    /// * `address` - The contract address
117    /// * `slot_keys` - The storage slot keys (uint256) to query
118    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    /// Adds a transaction proof to the batch
137    ///
138    /// # Arguments
139    ///
140    /// * `tx_hash` - The transaction hash to fetch proof for
141    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        // Build working sets
154        let mut exec_headers: BTreeSet<(u64, u64)> = BTreeSet::new(); // (network_id, block_number)
155        let mut beacon_headers: BTreeSet<(u64, u64)> = BTreeSet::new(); // (network_id, slot)
156
157        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        // add tx proofs to exec_headers
197        for proof in tx_proofs.clone() {
198            exec_headers.insert((proof.network_id, proof.block_number));
199        }
200
201        // Fetch headers via RPC only
202        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        // Build light-client request
240        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        // Single light-client call
256        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        // Parse block proof
265        let block_proof = parse_block_proof_value(lc_proof.block_proof.proof);
266        let block_proof = block_proof?;
267
268        // Index MMR proofs
269        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        // Build header proofs
275        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        // Fetch account proofs
301        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        // Fetch storage slot proofs
344        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}