Skip to main content

light_program_test/indexer/
test_indexer.rs

1use std::{collections::HashMap, fmt::Debug, time::Duration};
2
3#[cfg(feature = "devenv")]
4use account_compression::{
5    AddressMerkleTreeConfig, AddressQueueConfig, NullifierQueueConfig, StateMerkleTreeConfig,
6};
7
8use crate::accounts::test_accounts::TestAccounts;
9// Constants from account_compression and light_batched_merkle_tree for non-devenv mode
10pub(crate) const STATE_MERKLE_TREE_HEIGHT: u64 = 26;
11pub(crate) const STATE_MERKLE_TREE_CANOPY_DEPTH: u64 = 10;
12pub(crate) const STATE_MERKLE_TREE_ROOTS: u64 = 2400;
13pub(crate) const DEFAULT_BATCH_STATE_TREE_HEIGHT: usize = 32;
14pub(crate) const DEFAULT_BATCH_ADDRESS_TREE_HEIGHT: usize = 40;
15pub(crate) const DEFAULT_BATCH_ROOT_HISTORY_LEN: usize = 200;
16
17use async_trait::async_trait;
18use borsh::BorshDeserialize;
19#[cfg(feature = "devenv")]
20use light_batched_merkle_tree::merkle_tree::BatchedMerkleTreeAccount;
21#[cfg(feature = "devenv")]
22use light_client::rpc::{Rpc, RpcError};
23use light_client::{
24    fee::FeeConfig,
25    indexer::{
26        AccountProofInputs, Address, AddressMerkleTreeAccounts, AddressProofInputs,
27        AddressWithTree, CompressedAccount, CompressedTokenAccount, Context,
28        GetCompressedAccountsByOwnerConfig, GetCompressedTokenAccountsByOwnerOrDelegateOptions,
29        Indexer, IndexerError, IndexerRpcConfig, Items, ItemsWithCursor, MerkleProof,
30        NewAddressProofWithContext, OwnerBalance, PaginatedOptions, QueueElementsResult,
31        QueueElementsV2Options, Response, RetryConfig, RootIndex, SignatureWithMetadata,
32        StateMerkleTreeAccounts, TokenBalance, ValidityProofWithContext,
33    },
34};
35use light_compressed_account::{
36    compressed_account::{CompressedAccountWithMerkleContext, MerkleContext},
37    hash_chain::create_hash_chain_from_slice,
38    instruction_data::compressed_proof::CompressedProof,
39    tx_hash::create_tx_hash,
40    TreeType,
41};
42/// Discriminator for compressible accounts that store onchain_pubkey in the first 32 bytes of data.
43/// Re-exported from light_compressible for convenience.
44pub use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR;
45use light_event::event::PublicTransactionEvent;
46use light_hasher::{bigint::bigint_to_be_bytes_array, Poseidon};
47use light_merkle_tree_reference::MerkleTree;
48use light_prover_client::{
49    constants::{PROVE_PATH, SERVER_ADDRESS},
50    helpers::{big_int_to_string, bigint_to_u8_32, string_to_big_int},
51    proof::{compress_proof, deserialize_gnark_proof_json, proof_from_json_struct},
52    proof_type::ProofType,
53    proof_types::{
54        combined::{v1::CombinedJsonStruct as CombinedJsonStructLegacy, v2::CombinedJsonStruct},
55        inclusion::{
56            v1::{
57                BatchInclusionJsonStruct as BatchInclusionJsonStructLegacy,
58                InclusionProofInputs as InclusionProofInputsLegacy,
59            },
60            v2::{BatchInclusionJsonStruct, InclusionMerkleProofInputs, InclusionProofInputs},
61        },
62        non_inclusion::{
63            v1::{
64                BatchNonInclusionJsonStruct as BatchNonInclusionJsonStructLegacy,
65                NonInclusionProofInputs as NonInclusionProofInputsLegacy,
66            },
67            v2::{BatchNonInclusionJsonStruct, NonInclusionProofInputs},
68        },
69    },
70};
71use light_sdk::light_hasher::Hash;
72use light_token::compat::{TokenData, TokenDataWithMerkleContext};
73use log::info;
74use num_bigint::{BigInt, BigUint};
75use num_traits::FromBytes;
76use reqwest::Client;
77use solana_sdk::{
78    bs58,
79    pubkey::Pubkey,
80    signature::{Keypair, Signer},
81};
82
83#[cfg(feature = "devenv")]
84use super::address_tree::IndexedMerkleTreeVersion;
85use super::{
86    address_tree::AddressMerkleTreeBundle,
87    state_tree::{LeafIndexInfo, StateMerkleTreeBundle},
88};
89#[cfg(feature = "devenv")]
90use crate::accounts::{
91    address_tree::create_address_merkle_tree_and_queue_account,
92    address_tree_v2::create_batch_address_merkle_tree,
93    state_tree::create_state_merkle_tree_and_queue_account,
94    state_tree_v2::create_batched_state_merkle_tree,
95};
96use crate::indexer::TestIndexerExtensions;
97
98#[derive(Debug)]
99pub struct TestIndexer {
100    pub state_merkle_trees: Vec<StateMerkleTreeBundle>,
101    pub address_merkle_trees: Vec<AddressMerkleTreeBundle>,
102    pub payer: Keypair,
103    pub group_pda: Pubkey,
104    pub compressed_accounts: Vec<CompressedAccountWithMerkleContext>,
105    pub nullified_compressed_accounts: Vec<CompressedAccountWithMerkleContext>,
106    pub token_compressed_accounts: Vec<TokenDataWithMerkleContext>,
107    pub token_nullified_compressed_accounts: Vec<TokenDataWithMerkleContext>,
108    pub events: Vec<PublicTransactionEvent>,
109    /// Index mapping onchain_pubkey to compressed account index.
110    pub onchain_pubkey_index: HashMap<[u8; 32], usize>,
111}
112
113impl Clone for TestIndexer {
114    fn clone(&self) -> Self {
115        Self {
116            state_merkle_trees: self.state_merkle_trees.clone(),
117            address_merkle_trees: self.address_merkle_trees.clone(),
118            payer: self.payer.insecure_clone(),
119            group_pda: self.group_pda,
120            compressed_accounts: self.compressed_accounts.clone(),
121            nullified_compressed_accounts: self.nullified_compressed_accounts.clone(),
122            token_compressed_accounts: self.token_compressed_accounts.clone(),
123            token_nullified_compressed_accounts: self.token_nullified_compressed_accounts.clone(),
124            events: self.events.clone(),
125            onchain_pubkey_index: self.onchain_pubkey_index.clone(),
126        }
127    }
128}
129
130#[async_trait]
131impl Indexer for TestIndexer {
132    // TODO: add slot to test indexer struct
133    async fn get_indexer_slot(&self, _config: Option<RetryConfig>) -> Result<u64, IndexerError> {
134        // test indexer is always up to date
135        Ok(u64::MAX)
136    }
137
138    async fn get_multiple_compressed_account_proofs(
139        &self,
140        hashes: Vec<[u8; 32]>,
141        _config: Option<IndexerRpcConfig>,
142    ) -> Result<Response<Items<MerkleProof>>, IndexerError> {
143        info!("Getting proofs for {:?}", hashes);
144        let mut proofs: Vec<MerkleProof> = Vec::new();
145        hashes.iter().for_each(|hash| {
146            self.state_merkle_trees.iter().for_each(|tree| {
147                if let Some(leaf_index) = tree.merkle_tree.get_leaf_index(hash) {
148                    let proof = tree
149                        .merkle_tree
150                        .get_proof_of_leaf(leaf_index, true)
151                        .unwrap();
152                    proofs.push(MerkleProof {
153                        hash: *hash,
154                        leaf_index: leaf_index as u64,
155                        merkle_tree: tree.accounts.merkle_tree,
156                        proof: proof.to_vec(),
157                        root_seq: tree.merkle_tree.sequence_number as u64,
158                        root: *tree.merkle_tree.roots.last().unwrap(),
159                    });
160                }
161            })
162        });
163        Ok(Response {
164            context: Context {
165                slot: self.get_current_slot(),
166            },
167            value: Items { items: proofs },
168        })
169    }
170
171    async fn get_compressed_accounts_by_owner(
172        &self,
173        owner: &Pubkey,
174        _options: Option<GetCompressedAccountsByOwnerConfig>,
175        _config: Option<IndexerRpcConfig>,
176    ) -> Result<Response<ItemsWithCursor<CompressedAccount>>, IndexerError> {
177        let accounts_with_context = <TestIndexer as TestIndexerExtensions>::get_compressed_accounts_with_merkle_context_by_owner(self, owner);
178        let accounts: Result<Vec<CompressedAccount>, IndexerError> = accounts_with_context
179            .into_iter()
180            .map(|acc| acc.try_into())
181            .collect();
182
183        Ok(Response {
184            context: Context {
185                slot: self.get_current_slot(),
186            },
187            value: ItemsWithCursor {
188                items: accounts?,
189                cursor: None,
190            },
191        })
192    }
193
194    async fn get_compressed_account(
195        &self,
196        address: Address,
197        _config: Option<IndexerRpcConfig>,
198    ) -> Result<Response<Option<CompressedAccount>>, IndexerError> {
199        let account = self
200            .compressed_accounts
201            .iter()
202            .find(|acc| acc.compressed_account.address == Some(address));
203
204        let account_data = match account {
205            Some(acc) => Some(acc.clone().try_into()?),
206            None => None,
207        };
208
209        Ok(Response {
210            context: Context {
211                slot: self.get_current_slot(),
212            },
213            value: account_data,
214        })
215    }
216
217    async fn get_compressed_account_by_hash(
218        &self,
219        hash: Hash,
220        _config: Option<IndexerRpcConfig>,
221    ) -> Result<Response<Option<CompressedAccount>>, IndexerError> {
222        let res = self
223            .compressed_accounts
224            .iter()
225            .find(|acc| acc.hash() == Ok(hash));
226
227        // TODO: unify token accounts with compressed accounts.
228        let account = if res.is_none() {
229            let res = self
230                .token_compressed_accounts
231                .iter()
232                .find(|acc| acc.compressed_account.hash() == Ok(hash));
233            res.map(|x| &x.compressed_account)
234        } else {
235            res
236        };
237
238        let account_data = match account {
239            Some(acc) => Some(acc.clone().try_into()?),
240            None => None,
241        };
242
243        Ok(Response {
244            context: Context {
245                slot: self.get_current_slot(),
246            },
247            value: account_data,
248        })
249    }
250
251    async fn get_compressed_token_accounts_by_owner(
252        &self,
253        owner: &Pubkey,
254        options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
255        _config: Option<IndexerRpcConfig>,
256    ) -> Result<Response<ItemsWithCursor<CompressedTokenAccount>>, IndexerError> {
257        let mint = options.as_ref().and_then(|opts| opts.mint);
258        let token_accounts: Result<Vec<CompressedTokenAccount>, IndexerError> = self
259            .token_compressed_accounts
260            .iter()
261            .filter(|acc| {
262                acc.token_data.owner == *owner && mint.is_none_or(|m| acc.token_data.mint == m)
263            })
264            .map(|acc| CompressedTokenAccount::try_from(acc.clone()))
265            .collect();
266        let token_accounts = token_accounts?;
267        let token_accounts = if let Some(options) = options {
268            if let Some(limit) = options.limit {
269                token_accounts.into_iter().take(limit as usize).collect()
270            } else {
271                token_accounts
272            }
273        } else {
274            token_accounts
275        };
276
277        Ok(Response {
278            context: Context {
279                slot: self.get_current_slot(),
280            },
281            value: ItemsWithCursor {
282                items: token_accounts,
283                cursor: None,
284            },
285        })
286    }
287
288    async fn get_compressed_balance(
289        &self,
290        address: Option<Address>,
291        hash: Option<Hash>,
292        _config: Option<IndexerRpcConfig>,
293    ) -> Result<Response<u64>, IndexerError> {
294        let account_response = match (address, hash) {
295            (Some(addr), _) => self.get_compressed_account(addr, None).await?,
296            (_, Some(h)) => self.get_compressed_account_by_hash(h, None).await?,
297            _ => {
298                return Err(IndexerError::InvalidParameters(
299                    "Either address or hash must be provided".to_string(),
300                ))
301            }
302        };
303        let account = account_response
304            .value
305            .ok_or(IndexerError::AccountNotFound)?;
306        Ok(Response {
307            context: Context {
308                slot: self.get_current_slot(),
309            },
310            value: account.lamports,
311        })
312    }
313
314    async fn get_compressed_token_account_balance(
315        &self,
316        address: Option<Address>,
317        hash: Option<Hash>,
318        _config: Option<IndexerRpcConfig>,
319    ) -> Result<Response<u64>, IndexerError> {
320        let account = match (address, hash) {
321            (Some(address), _) => self
322                .token_compressed_accounts
323                .iter()
324                .find(|acc| acc.compressed_account.compressed_account.address == Some(address)),
325            (_, Some(hash)) => self
326                .token_compressed_accounts
327                .iter()
328                .find(|acc| acc.compressed_account.hash() == Ok(hash)),
329            (None, None) => {
330                return Err(IndexerError::InvalidParameters(
331                    "Either address or hash must be provided".to_string(),
332                ))
333            }
334        };
335
336        let amount = account
337            .map(|acc| acc.token_data.amount)
338            .ok_or(IndexerError::AccountNotFound)?;
339
340        Ok(Response {
341            context: Context {
342                slot: self.get_current_slot(),
343            },
344            value: amount,
345        })
346    }
347
348    async fn get_multiple_compressed_accounts(
349        &self,
350        addresses: Option<Vec<Address>>,
351        hashes: Option<Vec<Hash>>,
352        _config: Option<IndexerRpcConfig>,
353    ) -> Result<Response<Items<Option<CompressedAccount>>>, IndexerError> {
354        match (addresses, hashes) {
355            (Some(addresses), _) => {
356                let accounts: Result<Vec<Option<CompressedAccount>>, IndexerError> = addresses
357                    .iter()
358                    .map(|addr| {
359                        self.compressed_accounts
360                            .iter()
361                            .find(|acc| acc.compressed_account.address == Some(*addr))
362                            .map(|acc| acc.clone().try_into())
363                            .transpose()
364                    })
365                    .collect();
366                Ok(Response {
367                    context: Context {
368                        slot: self.get_current_slot(),
369                    },
370                    value: Items { items: accounts? },
371                })
372            }
373            (_, Some(hashes)) => {
374                let accounts: Result<Vec<Option<CompressedAccount>>, IndexerError> = hashes
375                    .iter()
376                    .map(|hash| {
377                        self.compressed_accounts
378                            .iter()
379                            .find(|acc| acc.hash() == Ok(*hash))
380                            .map(|acc| acc.clone().try_into())
381                            .transpose()
382                    })
383                    .collect();
384                Ok(Response {
385                    context: Context {
386                        slot: self.get_current_slot(),
387                    },
388                    value: Items { items: accounts? },
389                })
390            }
391            (None, None) => Err(IndexerError::InvalidParameters(
392                "Either addresses or hashes must be provided".to_string(),
393            )),
394        }
395    }
396
397    async fn get_compressed_token_balances_by_owner_v2(
398        &self,
399        owner: &Pubkey,
400        _options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
401        _config: Option<IndexerRpcConfig>,
402    ) -> Result<Response<ItemsWithCursor<TokenBalance>>, IndexerError> {
403        let mint = _options.as_ref().and_then(|opts| opts.mint);
404        let balances: Vec<TokenBalance> = self
405            .token_compressed_accounts
406            .iter()
407            .filter(|acc| {
408                acc.token_data.owner == *owner && mint.is_none_or(|m| acc.token_data.mint == m)
409            })
410            .fold(std::collections::HashMap::new(), |mut map, acc| {
411                *map.entry(acc.token_data.mint).or_insert(0) += acc.token_data.amount;
412                map
413            })
414            .into_iter()
415            .map(|(mint, balance)| TokenBalance { balance, mint })
416            .collect();
417
418        Ok(Response {
419            context: Context {
420                slot: self.get_current_slot(),
421            },
422            value: ItemsWithCursor {
423                items: balances,
424                cursor: None,
425            },
426        })
427    }
428
429    async fn get_compression_signatures_for_account(
430        &self,
431        _hash: Hash,
432        _config: Option<IndexerRpcConfig>,
433    ) -> Result<Response<Items<SignatureWithMetadata>>, IndexerError> {
434        todo!()
435    }
436
437    async fn get_multiple_new_address_proofs(
438        &self,
439        merkle_tree_pubkey: [u8; 32],
440        addresses: Vec<[u8; 32]>,
441        _config: Option<IndexerRpcConfig>,
442    ) -> Result<Response<Items<NewAddressProofWithContext>>, IndexerError> {
443        let proofs = self
444            ._get_multiple_new_address_proofs(merkle_tree_pubkey, addresses, false)
445            .await?;
446        Ok(Response {
447            context: Context {
448                slot: self.get_current_slot(),
449            },
450            value: Items { items: proofs },
451        })
452    }
453
454    async fn get_validity_proof(
455        &self,
456        hashes: Vec<[u8; 32]>,
457        new_addresses_with_trees: Vec<AddressWithTree>,
458        _config: Option<IndexerRpcConfig>,
459    ) -> Result<Response<ValidityProofWithContext>, IndexerError> {
460        #[cfg(feature = "v2")]
461        {
462            // V2 implementation with queue handling
463            let mut state_merkle_tree_pubkeys = Vec::new();
464
465            for hash in hashes.iter() {
466                let account = self.get_compressed_account_by_hash(*hash, None).await?;
467                let account_data = account.value.ok_or(IndexerError::AccountNotFound)?;
468                state_merkle_tree_pubkeys.push(account_data.tree_info.tree);
469            }
470            println!("state_merkle_tree_pubkeys {:?}", state_merkle_tree_pubkeys);
471            println!("hashes {:?}", hashes);
472            let mut proof_inputs = vec![];
473
474            let mut indices_to_remove = Vec::new();
475            // for all accounts in batched trees, check whether values are in tree or queue
476            let compressed_accounts = if !hashes.is_empty() && !state_merkle_tree_pubkeys.is_empty()
477            {
478                let zipped_accounts = hashes.iter().zip(state_merkle_tree_pubkeys.iter());
479
480                for (i, (compressed_account, state_merkle_tree_pubkey)) in
481                    zipped_accounts.enumerate()
482                {
483                    let accounts = self.state_merkle_trees.iter().find(|x| {
484                        x.accounts.merkle_tree == *state_merkle_tree_pubkey
485                            && x.tree_type == TreeType::StateV2
486                    });
487
488                    if let Some(accounts) = accounts {
489                        let queue_element = accounts
490                            .output_queue_elements
491                            .iter()
492                            .find(|(hash, _)| hash == compressed_account);
493                        println!("queue_element {:?}", queue_element);
494
495                        if let Some((_, index)) = queue_element {
496                            println!("index {:?}", index);
497                            println!(
498                                "accounts.output_queue_batch_size {:?}",
499                                accounts.output_queue_batch_size
500                            );
501                            if accounts.output_queue_batch_size.is_some()
502                                && accounts.leaf_index_in_queue_range(*index as usize)?
503                            {
504                                use light_client::indexer::RootIndex;
505
506                                indices_to_remove.push(i);
507                                proof_inputs.push(AccountProofInputs {
508                                    hash: *compressed_account,
509                                    root: [0u8; 32],
510                                    root_index: RootIndex::new_none(),
511                                    leaf_index: accounts
512                                        .output_queue_elements
513                                        .iter()
514                                        .position(|(x, _)| x == compressed_account)
515                                        .unwrap()
516                                        as u64,
517                                    tree_info: light_client::indexer::TreeInfo {
518                                        cpi_context: Some(accounts.accounts.cpi_context),
519                                        tree: accounts.accounts.merkle_tree,
520                                        queue: accounts.accounts.nullifier_queue,
521                                        next_tree_info: None,
522                                        tree_type: accounts.tree_type,
523                                    },
524                                })
525                            }
526                        }
527                    }
528                }
529
530                let compress_accounts = hashes
531                    .iter()
532                    .enumerate()
533                    .filter(|(i, _)| !indices_to_remove.contains(i))
534                    .map(|(_, x)| *x)
535                    .collect::<Vec<[u8; 32]>>();
536
537                if compress_accounts.is_empty() {
538                    None
539                } else {
540                    Some(compress_accounts)
541                }
542            } else {
543                None
544            };
545
546            // Get the basic validity proof if needed
547            let rpc_result: Option<ValidityProofWithContext> = if (compressed_accounts.is_some()
548                && !compressed_accounts.as_ref().unwrap().is_empty())
549                || !new_addresses_with_trees.is_empty()
550            {
551                Some(
552                    self._get_validity_proof_v1_implementation(
553                        compressed_accounts.unwrap_or_default(),
554                        new_addresses_with_trees,
555                    )
556                    .await?,
557                )
558            } else {
559                None
560            };
561
562            // Handle root indices with queue considerations
563            let addresses = if let Some(rpc_result) = rpc_result.as_ref() {
564                rpc_result.addresses.to_vec()
565            } else {
566                Vec::new()
567            };
568            let accounts = {
569                let mut root_indices = if let Some(rpc_result) = rpc_result.as_ref() {
570                    rpc_result.accounts.to_vec()
571                } else {
572                    Vec::new()
573                };
574                #[cfg(debug_assertions)]
575                {
576                    if std::env::var("RUST_BACKTRACE").is_ok() {
577                        println!("get_validit_proof: rpc_result {:?}", rpc_result);
578                    }
579                }
580
581                // Reinsert proof_inputs at their original positions in forward order
582                for (proof_input, &index) in proof_inputs.iter().zip(indices_to_remove.iter()) {
583                    if root_indices.len() <= index {
584                        root_indices.push(proof_input.clone());
585                    } else {
586                        root_indices.insert(index, proof_input.clone());
587                    }
588                }
589                root_indices
590            };
591
592            Ok(Response {
593                context: Context {
594                    slot: self.get_current_slot(),
595                },
596                value: ValidityProofWithContext {
597                    accounts,
598                    addresses,
599                    proof: rpc_result
600                        .map(|rpc_result| rpc_result.proof.0.unwrap())
601                        .into(),
602                },
603            })
604        }
605
606        #[cfg(not(feature = "v2"))]
607        {
608            // V1 implementation - direct call to V1 logic
609            let result = self
610                ._get_validity_proof_v1_implementation(hashes, new_addresses_with_trees)
611                .await?;
612            Ok(Response {
613                context: Context {
614                    slot: self.get_current_slot(),
615                },
616                value: result,
617            })
618        }
619    }
620
621    async fn get_queue_elements(
622        &mut self,
623        _merkle_tree_pubkey: [u8; 32],
624        _options: QueueElementsV2Options,
625        _config: Option<IndexerRpcConfig>,
626    ) -> Result<Response<QueueElementsResult>, IndexerError> {
627        #[cfg(not(feature = "v2"))]
628        unimplemented!("get_queue_elements");
629        #[cfg(feature = "v2")]
630        {
631            use std::collections::HashMap;
632
633            use light_client::indexer::{
634                AddressQueueData, InputQueueData, OutputQueueData, StateQueueData,
635            };
636            use light_hasher::bigint::bigint_to_be_bytes_array;
637
638            let merkle_tree_pubkey = _merkle_tree_pubkey;
639            let options = _options;
640            let pubkey = Pubkey::new_from_array(merkle_tree_pubkey);
641
642            // Helper function to encode node index: (level << 56) | position
643            fn encode_node_index(level: u8, position: u64) -> u64 {
644                ((level as u64) << 56) | position
645            }
646
647            // Helper function to add proof nodes to the deduplicated node map
648            fn add_proof_to_node_map(
649                proof: &[[u8; 32]],
650                leaf_index: u64,
651                node_map: &mut HashMap<u64, [u8; 32]>,
652            ) {
653                let mut pos = leaf_index;
654                for (level, node_hash) in proof.iter().enumerate() {
655                    let sibling_pos = if pos.is_multiple_of(2) {
656                        pos + 1
657                    } else {
658                        pos - 1
659                    };
660                    let encoded = encode_node_index(level as u8, sibling_pos);
661                    node_map.entry(encoded).or_insert(*node_hash);
662                    pos /= 2;
663                }
664            }
665
666            // Check if this is an address tree
667            let address_tree_bundle = self
668                .address_merkle_trees
669                .iter()
670                .find(|x| x.accounts.merkle_tree == pubkey);
671
672            if let Some(address_tree_bundle) = address_tree_bundle {
673                // For address trees, return address queue data if requested
674                let address_queue = if let Some(limit) = options.address_queue_limit {
675                    let start = options.address_queue_start_index.unwrap_or(0) as usize;
676                    let end = std::cmp::min(
677                        start + limit as usize,
678                        address_tree_bundle.queue_elements.len(),
679                    );
680                    let addresses = address_tree_bundle.queue_elements[start..end].to_vec();
681
682                    // Build low element data for each address
683                    let mut low_element_values = Vec::with_capacity(addresses.len());
684                    let mut low_element_next_values = Vec::with_capacity(addresses.len());
685                    let mut low_element_indices = Vec::with_capacity(addresses.len());
686                    let mut low_element_next_indices = Vec::with_capacity(addresses.len());
687
688                    // Collect all nodes for deduplication
689                    let mut node_map: HashMap<u64, [u8; 32]> = HashMap::new();
690
691                    for address in &addresses {
692                        let address_biguint = BigUint::from_be_bytes(address.as_slice());
693                        let (old_low_element, old_low_next_value) = address_tree_bundle
694                            .find_low_element_for_nonexistent(&address_biguint)?;
695                        let proof =
696                            address_tree_bundle.get_proof_of_leaf(old_low_element.index, true)?;
697
698                        add_proof_to_node_map(&proof, old_low_element.index as u64, &mut node_map);
699
700                        low_element_values
701                            .push(bigint_to_be_bytes_array(&old_low_element.value).unwrap());
702                        low_element_next_values
703                            .push(bigint_to_be_bytes_array(&old_low_next_value).unwrap());
704                        low_element_indices.push(old_low_element.index as u64);
705                        low_element_next_indices.push(old_low_element.next_index as u64);
706                    }
707
708                    // Convert node map to sorted vectors
709                    let mut nodes: Vec<u64> = node_map.keys().copied().collect();
710                    nodes.sort();
711                    let node_hashes: Vec<[u8; 32]> = nodes.iter().map(|k| node_map[k]).collect();
712
713                    Some(AddressQueueData {
714                        addresses,
715                        low_element_values,
716                        low_element_next_values,
717                        low_element_indices,
718                        low_element_next_indices,
719                        nodes,
720                        node_hashes,
721                        initial_root: address_tree_bundle.root(),
722                        leaves_hash_chains: Vec::new(),
723                        subtrees: address_tree_bundle.get_subtrees(),
724                        start_index: start as u64,
725                        root_seq: address_tree_bundle.sequence_number(),
726                    })
727                } else {
728                    None
729                };
730
731                return Ok(Response {
732                    context: Context {
733                        slot: self.get_current_slot(),
734                    },
735                    value: QueueElementsResult {
736                        state_queue: None,
737                        address_queue,
738                    },
739                });
740            }
741
742            // Check if this is a state tree
743            let state_tree_bundle = self
744                .state_merkle_trees
745                .iter_mut()
746                .find(|x| x.accounts.merkle_tree == pubkey || x.accounts.nullifier_queue == pubkey);
747
748            if let Some(state_tree_bundle) = state_tree_bundle {
749                // Collect nodes for deduplication across both queues
750                let mut node_map: HashMap<u64, [u8; 32]> = HashMap::new();
751
752                // Build output queue data if requested
753                let output_queue = if let Some(limit) = options.output_queue_limit {
754                    let start = options.output_queue_start_index.unwrap_or(0) as usize;
755                    let end = std::cmp::min(
756                        start + limit as usize,
757                        state_tree_bundle.output_queue_elements.len(),
758                    );
759                    let queue_elements =
760                        state_tree_bundle.output_queue_elements[start..end].to_vec();
761
762                    let leaf_indices: Vec<u64> =
763                        queue_elements.iter().map(|(_, index)| *index).collect();
764                    let account_hashes: Vec<[u8; 32]> =
765                        queue_elements.iter().map(|(hash, _)| *hash).collect();
766
767                    // Get old leaves at those indices and collect proof nodes
768                    let old_leaves: Vec<[u8; 32]> = leaf_indices
769                        .iter()
770                        .map(|index| {
771                            // Extend merkle tree if needed
772                            while state_tree_bundle.merkle_tree.leaves().len() <= *index as usize {
773                                state_tree_bundle.merkle_tree.append(&[0u8; 32]).unwrap();
774                            }
775                            let leaf = state_tree_bundle
776                                .merkle_tree
777                                .get_leaf(*index as usize)
778                                .unwrap_or_default();
779
780                            // Get proof and add to node map
781                            if let Ok(proof) = state_tree_bundle
782                                .merkle_tree
783                                .get_proof_of_leaf(*index as usize, true)
784                            {
785                                add_proof_to_node_map(&proof, *index, &mut node_map);
786                            }
787                            leaf
788                        })
789                        .collect();
790
791                    Some(OutputQueueData {
792                        leaf_indices,
793                        account_hashes,
794                        old_leaves,
795                        first_queue_index: start as u64,
796                        next_index: state_tree_bundle.merkle_tree.get_next_index() as u64,
797                        leaves_hash_chains: Vec::new(),
798                    })
799                } else {
800                    None
801                };
802
803                // Build input queue data if requested
804                let input_queue = if let Some(limit) = options.input_queue_limit {
805                    let start = options.input_queue_start_index.unwrap_or(0) as usize;
806                    let end = std::cmp::min(
807                        start + limit as usize,
808                        state_tree_bundle.input_leaf_indices.len(),
809                    );
810                    let queue_elements = state_tree_bundle.input_leaf_indices[start..end].to_vec();
811
812                    let leaf_indices: Vec<u64> = queue_elements
813                        .iter()
814                        .map(|info| info.leaf_index as u64)
815                        .collect();
816                    let account_hashes: Vec<[u8; 32]> =
817                        queue_elements.iter().map(|info| info.leaf).collect();
818                    let tx_hashes: Vec<[u8; 32]> =
819                        queue_elements.iter().map(|info| info.tx_hash).collect();
820
821                    // Get current leaves and collect proof nodes
822                    let current_leaves: Vec<[u8; 32]> = leaf_indices
823                        .iter()
824                        .map(|index| {
825                            // Extend merkle tree if needed
826                            while state_tree_bundle.merkle_tree.leaves().len() <= *index as usize {
827                                state_tree_bundle.merkle_tree.append(&[0u8; 32]).unwrap();
828                            }
829                            let leaf = state_tree_bundle
830                                .merkle_tree
831                                .get_leaf(*index as usize)
832                                .unwrap_or_default();
833
834                            // Get proof and add to node map
835                            if let Ok(proof) = state_tree_bundle
836                                .merkle_tree
837                                .get_proof_of_leaf(*index as usize, true)
838                            {
839                                add_proof_to_node_map(&proof, *index, &mut node_map);
840                            }
841                            leaf
842                        })
843                        .collect();
844
845                    Some(InputQueueData {
846                        leaf_indices,
847                        account_hashes,
848                        current_leaves,
849                        tx_hashes,
850                        nullifiers: Vec::new(),
851                        first_queue_index: start as u64,
852                        leaves_hash_chains: Vec::new(),
853                    })
854                } else {
855                    None
856                };
857
858                // Build state queue result if either input or output queue was requested
859                let state_queue = if output_queue.is_some() || input_queue.is_some() {
860                    // Convert node map to sorted vectors
861                    let mut nodes: Vec<u64> = node_map.keys().copied().collect();
862                    nodes.sort();
863                    let node_hashes: Vec<[u8; 32]> = nodes.iter().map(|k| node_map[k]).collect();
864
865                    Some(StateQueueData {
866                        nodes,
867                        node_hashes,
868                        initial_root: state_tree_bundle.merkle_tree.root(),
869                        root_seq: state_tree_bundle.merkle_tree.sequence_number as u64,
870                        output_queue,
871                        input_queue,
872                    })
873                } else {
874                    None
875                };
876
877                return Ok(Response {
878                    context: Context {
879                        slot: self.get_current_slot(),
880                    },
881                    value: QueueElementsResult {
882                        state_queue,
883                        address_queue: None,
884                    },
885                });
886            }
887
888            Err(IndexerError::InvalidParameters(
889                "Merkle tree not found".to_string(),
890            ))
891        }
892    }
893
894    async fn get_queue_info(
895        &self,
896        _config: Option<IndexerRpcConfig>,
897    ) -> Result<Response<light_client::indexer::QueueInfoResult>, IndexerError> {
898        unimplemented!("get_queue_info")
899    }
900
901    async fn get_subtrees(
902        &self,
903        _merkle_tree_pubkey: [u8; 32],
904        _config: Option<IndexerRpcConfig>,
905    ) -> Result<Response<Items<[u8; 32]>>, IndexerError> {
906        #[cfg(not(feature = "v2"))]
907        unimplemented!("get_subtrees");
908        #[cfg(feature = "v2")]
909        {
910            let merkle_tree_pubkey = Pubkey::new_from_array(_merkle_tree_pubkey);
911            let address_tree_bundle = self
912                .address_merkle_trees
913                .iter()
914                .find(|x| x.accounts.merkle_tree == merkle_tree_pubkey);
915            if let Some(address_tree_bundle) = address_tree_bundle {
916                Ok(Response {
917                    context: Context {
918                        slot: self.get_current_slot(),
919                    },
920                    value: Items {
921                        items: address_tree_bundle.get_subtrees(),
922                    },
923                })
924            } else {
925                let state_tree_bundle = self
926                    .state_merkle_trees
927                    .iter()
928                    .find(|x| x.accounts.merkle_tree == merkle_tree_pubkey);
929                if let Some(state_tree_bundle) = state_tree_bundle {
930                    Ok(Response {
931                        context: Context {
932                            slot: self.get_current_slot(),
933                        },
934                        value: Items {
935                            items: state_tree_bundle.merkle_tree.get_subtrees(),
936                        },
937                    })
938                } else {
939                    Err(IndexerError::InvalidParameters(
940                        "Merkle tree not found".to_string(),
941                    ))
942                }
943            }
944        }
945    }
946
947    // New required trait methods
948    async fn get_compressed_balance_by_owner(
949        &self,
950        _owner: &Pubkey,
951        _config: Option<IndexerRpcConfig>,
952    ) -> Result<Response<u64>, IndexerError> {
953        todo!("get_compressed_balance_by_owner not implemented")
954    }
955
956    async fn get_compressed_mint_token_holders(
957        &self,
958        _mint: &Pubkey,
959        _options: Option<PaginatedOptions>,
960        _config: Option<IndexerRpcConfig>,
961    ) -> Result<Response<ItemsWithCursor<OwnerBalance>>, IndexerError> {
962        todo!("get_compressed_mint_token_holders not implemented")
963    }
964
965    async fn get_compressed_token_accounts_by_delegate(
966        &self,
967        _delegate: &Pubkey,
968        _options: Option<GetCompressedTokenAccountsByOwnerOrDelegateOptions>,
969        _config: Option<IndexerRpcConfig>,
970    ) -> Result<Response<ItemsWithCursor<CompressedTokenAccount>>, IndexerError> {
971        todo!("get_compressed_token_accounts_by_delegate not implemented")
972    }
973
974    async fn get_compression_signatures_for_address(
975        &self,
976        _address: &[u8; 32],
977        _options: Option<PaginatedOptions>,
978        _config: Option<IndexerRpcConfig>,
979    ) -> Result<Response<ItemsWithCursor<SignatureWithMetadata>>, IndexerError> {
980        todo!("get_compression_signatures_for_address not implemented")
981    }
982
983    async fn get_compression_signatures_for_owner(
984        &self,
985        _owner: &Pubkey,
986        _options: Option<PaginatedOptions>,
987        _config: Option<IndexerRpcConfig>,
988    ) -> Result<Response<ItemsWithCursor<SignatureWithMetadata>>, IndexerError> {
989        todo!("get_compression_signatures_for_owner not implemented")
990    }
991
992    async fn get_compression_signatures_for_token_owner(
993        &self,
994        _owner: &Pubkey,
995        _options: Option<PaginatedOptions>,
996        _config: Option<IndexerRpcConfig>,
997    ) -> Result<Response<ItemsWithCursor<SignatureWithMetadata>>, IndexerError> {
998        todo!("get_compression_signatures_for_token_owner not implemented")
999    }
1000
1001    async fn get_indexer_health(&self, _config: Option<RetryConfig>) -> Result<bool, IndexerError> {
1002        Ok(true) // Test indexer is always healthy
1003    }
1004}
1005
1006#[async_trait]
1007impl TestIndexerExtensions for TestIndexer {
1008    fn get_address_merkle_trees(&self) -> &Vec<AddressMerkleTreeBundle> {
1009        &self.address_merkle_trees
1010    }
1011
1012    fn get_address_merkle_tree(
1013        &self,
1014        merkle_tree_pubkey: Pubkey,
1015    ) -> Option<&AddressMerkleTreeBundle> {
1016        self.address_merkle_trees
1017            .iter()
1018            .find(|x| x.accounts.merkle_tree == merkle_tree_pubkey)
1019    }
1020
1021    /// deserializes an event
1022    /// adds the output_compressed_accounts to the compressed_accounts
1023    /// removes the input_compressed_accounts from the compressed_accounts
1024    /// adds the input_compressed_accounts to the nullified_compressed_accounts
1025    /// deserialiazes token data from the output_compressed_accounts
1026    /// adds the token_compressed_accounts to the token_compressed_accounts
1027    fn add_compressed_accounts_with_token_data(
1028        &mut self,
1029        slot: u64,
1030        event: &PublicTransactionEvent,
1031    ) {
1032        TestIndexer::add_event_and_compressed_accounts(self, slot, event);
1033    }
1034
1035    fn account_nullified(&mut self, merkle_tree_pubkey: Pubkey, account_hash: &str) {
1036        let decoded_hash: [u8; 32] = bs58::decode(account_hash)
1037            .into_vec()
1038            .unwrap()
1039            .as_slice()
1040            .try_into()
1041            .unwrap();
1042
1043        if let Some(state_tree_bundle) = self
1044            .state_merkle_trees
1045            .iter_mut()
1046            .find(|x| x.accounts.merkle_tree == merkle_tree_pubkey)
1047        {
1048            if let Some(leaf_index) = state_tree_bundle.merkle_tree.get_leaf_index(&decoded_hash) {
1049                state_tree_bundle
1050                    .merkle_tree
1051                    .update(&[0u8; 32], leaf_index)
1052                    .unwrap();
1053            }
1054        }
1055    }
1056
1057    fn address_tree_updated(
1058        &mut self,
1059        merkle_tree_pubkey: Pubkey,
1060        context: &NewAddressProofWithContext,
1061    ) {
1062        info!("Updating address tree...");
1063        let pos = self
1064            .address_merkle_trees
1065            .iter()
1066            .position(|x| x.accounts.merkle_tree == merkle_tree_pubkey)
1067            .unwrap();
1068        let new_low_element = context.new_low_element.clone().unwrap();
1069        let new_element = context.new_element.clone().unwrap();
1070        let new_element_next_value = context.new_element_next_value.clone().unwrap();
1071        // It can only be v1 address tree because proof with context has len 16.
1072        self.address_merkle_trees[pos]
1073            .get_v1_indexed_merkle_tree_mut()
1074            .expect("Failed to get v1 indexed merkle tree.")
1075            .update(&new_low_element, &new_element, &new_element_next_value)
1076            .unwrap();
1077        self.address_merkle_trees[pos]
1078            .append_with_low_element_index(new_low_element.index, &new_element.value)
1079            .unwrap();
1080        info!("Address tree updated");
1081    }
1082
1083    fn get_state_merkle_tree_accounts(&self, pubkeys: &[Pubkey]) -> Vec<StateMerkleTreeAccounts> {
1084        pubkeys
1085            .iter()
1086            .map(|x| {
1087                self.state_merkle_trees
1088                    .iter()
1089                    .find(|y| y.accounts.merkle_tree == *x || y.accounts.nullifier_queue == *x)
1090                    .unwrap()
1091                    .accounts
1092            })
1093            .collect::<Vec<_>>()
1094    }
1095
1096    fn get_state_merkle_trees(&self) -> &Vec<StateMerkleTreeBundle> {
1097        &self.state_merkle_trees
1098    }
1099
1100    fn get_state_merkle_trees_mut(&mut self) -> &mut Vec<StateMerkleTreeBundle> {
1101        &mut self.state_merkle_trees
1102    }
1103
1104    fn get_address_merkle_trees_mut(&mut self) -> &mut Vec<AddressMerkleTreeBundle> {
1105        &mut self.address_merkle_trees
1106    }
1107
1108    fn get_token_compressed_accounts(&self) -> &Vec<TokenDataWithMerkleContext> {
1109        &self.token_compressed_accounts
1110    }
1111
1112    fn get_group_pda(&self) -> &Pubkey {
1113        &self.group_pda
1114    }
1115
1116    fn add_address_merkle_tree_accounts(
1117        &mut self,
1118        merkle_tree_keypair: &Keypair,
1119        queue_keypair: &Keypair,
1120        _owning_program_id: Option<Pubkey>,
1121    ) -> AddressMerkleTreeAccounts {
1122        info!("Adding address merkle tree accounts...");
1123        let address_merkle_tree_accounts = AddressMerkleTreeAccounts {
1124            merkle_tree: merkle_tree_keypair.pubkey(),
1125            queue: queue_keypair.pubkey(),
1126        };
1127        self.address_merkle_trees
1128            .push(Self::add_address_merkle_tree_bundle(address_merkle_tree_accounts).unwrap());
1129        info!(
1130            "Address merkle tree accounts added. Total: {}",
1131            self.address_merkle_trees.len()
1132        );
1133        address_merkle_tree_accounts
1134    }
1135
1136    fn get_compressed_accounts_with_merkle_context_by_owner(
1137        &self,
1138        owner: &Pubkey,
1139    ) -> Vec<CompressedAccountWithMerkleContext> {
1140        self.compressed_accounts
1141            .iter()
1142            .filter(|x| x.compressed_account.owner.to_bytes() == owner.to_bytes())
1143            .cloned()
1144            .collect()
1145    }
1146
1147    fn add_state_bundle(&mut self, state_bundle: StateMerkleTreeBundle) {
1148        Self::get_state_merkle_trees_mut(self).push(state_bundle);
1149    }
1150
1151    fn add_event_and_compressed_accounts(
1152        &mut self,
1153        slot: u64,
1154        event: &PublicTransactionEvent,
1155    ) -> (
1156        Vec<CompressedAccountWithMerkleContext>,
1157        Vec<TokenDataWithMerkleContext>,
1158    ) {
1159        let mut compressed_accounts = Vec::new();
1160        let mut token_compressed_accounts = Vec::new();
1161        let event_inputs_len = event.input_compressed_account_hashes.len();
1162        let event_outputs_len = event.output_compressed_account_hashes.len();
1163        for i in 0..std::cmp::max(event_inputs_len, event_outputs_len) {
1164            self.process_v1_compressed_account(
1165                slot,
1166                event,
1167                i,
1168                &mut token_compressed_accounts,
1169                &mut compressed_accounts,
1170            );
1171        }
1172
1173        self.events.push(event.clone());
1174        (compressed_accounts, token_compressed_accounts)
1175    }
1176
1177    fn get_proof_by_index(&mut self, merkle_tree_pubkey: Pubkey, index: u64) -> MerkleProof {
1178        let bundle = self
1179            .state_merkle_trees
1180            .iter_mut()
1181            .find(|x| x.accounts.merkle_tree == merkle_tree_pubkey)
1182            .unwrap();
1183
1184        while bundle.merkle_tree.leaves().len() <= index as usize {
1185            bundle.merkle_tree.append(&[0u8; 32]).unwrap();
1186        }
1187
1188        let leaf = match bundle.merkle_tree.get_leaf(index as usize) {
1189            Ok(leaf) => leaf,
1190            Err(_) => {
1191                bundle.merkle_tree.append(&[0u8; 32]).unwrap();
1192                bundle.merkle_tree.get_leaf(index as usize).unwrap()
1193            }
1194        };
1195
1196        let proof = bundle
1197            .merkle_tree
1198            .get_proof_of_leaf(index as usize, true)
1199            .unwrap()
1200            .to_vec();
1201
1202        MerkleProof {
1203            hash: leaf,
1204            leaf_index: index,
1205            merkle_tree: merkle_tree_pubkey,
1206            proof,
1207            root_seq: bundle.merkle_tree.sequence_number as u64,
1208            root: bundle.merkle_tree.root(),
1209        }
1210    }
1211
1212    #[cfg(feature = "devenv")]
1213    async fn finalize_batched_address_tree_update(
1214        &mut self,
1215        merkle_tree_pubkey: Pubkey,
1216        account_data: &mut [u8],
1217    ) {
1218        let onchain_account =
1219            BatchedMerkleTreeAccount::address_from_bytes(account_data, &merkle_tree_pubkey.into())
1220                .unwrap();
1221        let address_tree = self
1222            .address_merkle_trees
1223            .iter_mut()
1224            .find(|x| x.accounts.merkle_tree == merkle_tree_pubkey)
1225            .unwrap();
1226        let address_tree_index = address_tree.right_most_index();
1227        let onchain_next_index = onchain_account.next_index;
1228        let diff_onchain_indexer = onchain_next_index - address_tree_index as u64;
1229        let addresses = address_tree.queue_elements[0..diff_onchain_indexer as usize].to_vec();
1230        for _ in 0..diff_onchain_indexer {
1231            address_tree.queue_elements.remove(0);
1232        }
1233        for new_element_value in &addresses {
1234            address_tree
1235                .append(&BigUint::from_bytes_be(new_element_value))
1236                .unwrap();
1237        }
1238        match &mut address_tree.merkle_tree {
1239            IndexedMerkleTreeVersion::V2(tree) => tree.merkle_tree.num_root_updates += 1,
1240            IndexedMerkleTreeVersion::V1(_) => {
1241                unimplemented!("finalize_batched_address_tree_update not implemented for v1 trees.")
1242            }
1243        }
1244        let onchain_root = onchain_account.root_history.last().unwrap();
1245        let new_root = address_tree.root();
1246        assert_eq!(*onchain_root, new_root);
1247    }
1248}
1249
1250impl TestIndexer {
1251    fn get_current_slot(&self) -> u64 {
1252        // For testing, we can use a fixed slot or MAX
1253        u64::MAX
1254    }
1255
1256    pub async fn init_from_acounts(
1257        payer: &Keypair,
1258        env: &TestAccounts,
1259        output_queue_batch_size: usize,
1260    ) -> Self {
1261        // Create a vector of StateMerkleTreeAccounts from all v1 and v2 state trees
1262        let mut state_merkle_tree_accounts = env.v1_state_trees.clone();
1263
1264        // Add v2 state trees converting from StateMerkleTreeAccountsV2 to StateMerkleTreeAccounts
1265        for v2_state_tree in &env.v2_state_trees {
1266            state_merkle_tree_accounts.push(StateMerkleTreeAccounts {
1267                merkle_tree: v2_state_tree.merkle_tree,
1268                nullifier_queue: v2_state_tree.output_queue,
1269                cpi_context: v2_state_tree.cpi_context,
1270                tree_type: TreeType::StateV2,
1271            });
1272        }
1273
1274        // Create a vector of AddressMerkleTreeAccounts from all v1 address trees
1275        let mut address_merkle_tree_accounts = env.v1_address_trees.clone();
1276
1277        // Add v2 address trees (each entry is both the merkle tree and queue)
1278        for &v2_address_tree in &env.v2_address_trees {
1279            address_merkle_tree_accounts.push(AddressMerkleTreeAccounts {
1280                merkle_tree: v2_address_tree,
1281                queue: v2_address_tree,
1282            });
1283        }
1284
1285        Self::new(
1286            state_merkle_tree_accounts,
1287            address_merkle_tree_accounts,
1288            payer.insecure_clone(),
1289            env.protocol.group_pda,
1290            output_queue_batch_size,
1291        )
1292        .await
1293    }
1294
1295    pub async fn new(
1296        state_merkle_tree_accounts: Vec<StateMerkleTreeAccounts>,
1297        address_merkle_tree_accounts: Vec<AddressMerkleTreeAccounts>,
1298        payer: Keypair,
1299        group_pda: Pubkey,
1300        output_queue_batch_size: usize,
1301    ) -> Self {
1302        let mut state_merkle_trees = Vec::new();
1303        for state_merkle_tree_account in state_merkle_tree_accounts.iter() {
1304            let (tree_type, merkle_tree, output_queue_batch_size) =
1305                if state_merkle_tree_account.tree_type == TreeType::StateV2 {
1306                    let merkle_tree = Box::new(MerkleTree::<Poseidon>::new_with_history(
1307                        DEFAULT_BATCH_STATE_TREE_HEIGHT,
1308                        0,
1309                        0,
1310                        DEFAULT_BATCH_ROOT_HISTORY_LEN,
1311                    ));
1312                    (
1313                        TreeType::StateV2,
1314                        merkle_tree,
1315                        Some(output_queue_batch_size),
1316                    )
1317                } else {
1318                    let merkle_tree = Box::new(MerkleTree::<Poseidon>::new_with_history(
1319                        STATE_MERKLE_TREE_HEIGHT as usize,
1320                        STATE_MERKLE_TREE_CANOPY_DEPTH as usize,
1321                        0,
1322                        STATE_MERKLE_TREE_ROOTS as usize,
1323                    ));
1324                    (TreeType::StateV1, merkle_tree, None)
1325                };
1326
1327            state_merkle_trees.push(StateMerkleTreeBundle {
1328                accounts: *state_merkle_tree_account,
1329                merkle_tree,
1330                rollover_fee: FeeConfig::default().state_merkle_tree_rollover as i64,
1331                tree_type,
1332                output_queue_elements: vec![],
1333                input_leaf_indices: vec![],
1334                output_queue_batch_size,
1335                num_inserted_batches: 0,
1336            });
1337        }
1338
1339        let mut address_merkle_trees = Vec::new();
1340        for address_merkle_tree_account in address_merkle_tree_accounts {
1341            address_merkle_trees
1342                .push(Self::add_address_merkle_tree_bundle(address_merkle_tree_account).unwrap());
1343        }
1344
1345        Self {
1346            state_merkle_trees,
1347            address_merkle_trees,
1348            payer,
1349            compressed_accounts: vec![],
1350            nullified_compressed_accounts: vec![],
1351            events: vec![],
1352            token_compressed_accounts: vec![],
1353            token_nullified_compressed_accounts: vec![],
1354            group_pda,
1355            onchain_pubkey_index: HashMap::new(),
1356        }
1357    }
1358
1359    /// Extract onchain_pubkey from compressed account data if it has the decompressed discriminator.
1360    /// Compressible accounts store the on-chain PDA pubkey in the first 32 bytes of data.
1361    fn extract_onchain_pubkey_from_data(
1362        data: Option<&light_compressed_account::compressed_account::CompressedAccountData>,
1363    ) -> Option<[u8; 32]> {
1364        let data = data?;
1365        // Check discriminator matches DECOMPRESSED_PDA_DISCRIMINATOR
1366        if data.discriminator == DECOMPRESSED_PDA_DISCRIMINATOR && data.data.len() >= 32 {
1367            // onchain_pubkey is stored in the first 32 bytes of data (after discriminator)
1368            data.data[..32].try_into().ok()
1369        } else {
1370            None
1371        }
1372    }
1373
1374    /// Find a compressed account by its on-chain pubkey.
1375    /// This mirrors Photon's lookup by onchain_pubkey column.
1376    pub fn find_compressed_account_by_onchain_pubkey(
1377        &self,
1378        onchain_pubkey: &[u8; 32],
1379    ) -> Option<&CompressedAccountWithMerkleContext> {
1380        let matches: Vec<_> = self
1381            .compressed_accounts
1382            .iter()
1383            .filter(|acc| {
1384                Self::extract_onchain_pubkey_from_data(acc.compressed_account.data.as_ref())
1385                    .as_ref()
1386                    == Some(onchain_pubkey)
1387            })
1388            .collect();
1389
1390        debug_assert!(
1391            matches.len() <= 1,
1392            "find_compressed_account_by_onchain_pubkey: found {} matches, expected at most 1",
1393            matches.len()
1394        );
1395
1396        matches.into_iter().next()
1397    }
1398
1399    /// Find multiple compressed accounts by their on-chain pubkeys.
1400    pub fn find_multiple_compressed_accounts_by_onchain_pubkeys(
1401        &self,
1402        onchain_pubkeys: &[[u8; 32]],
1403    ) -> Vec<Option<&CompressedAccountWithMerkleContext>> {
1404        onchain_pubkeys
1405            .iter()
1406            .map(|pubkey| self.find_compressed_account_by_onchain_pubkey(pubkey))
1407            .collect()
1408    }
1409
1410    /// Find a token compressed account by its on-chain pubkey.
1411    pub fn find_token_account_by_onchain_pubkey(
1412        &self,
1413        onchain_pubkey: &[u8; 32],
1414    ) -> Option<&TokenDataWithMerkleContext> {
1415        let matches: Vec<_> = self
1416            .token_compressed_accounts
1417            .iter()
1418            .filter(|acc| {
1419                Self::extract_onchain_pubkey_from_data(
1420                    acc.compressed_account.compressed_account.data.as_ref(),
1421                )
1422                .as_ref()
1423                    == Some(onchain_pubkey)
1424            })
1425            .collect();
1426
1427        debug_assert!(
1428            matches.len() <= 1,
1429            "find_token_account_by_onchain_pubkey: found {} matches, expected at most 1",
1430            matches.len()
1431        );
1432
1433        matches.into_iter().next()
1434    }
1435
1436    /// Find a compressed account by its PDA pubkey
1437    pub fn find_compressed_account_by_pda_seed(
1438        &self,
1439        pda_pubkey: &[u8; 32],
1440    ) -> Option<&CompressedAccountWithMerkleContext> {
1441        // Try each address tree to find an account whose address matches
1442        for address_tree in &self.address_merkle_trees {
1443            let tree_pubkey = address_tree.accounts.merkle_tree.to_bytes();
1444
1445            // For each compressed account with an address, check if it was derived from this seed
1446            for acc in &self.compressed_accounts {
1447                if let Some(address) = acc.compressed_account.address {
1448                    // Try deriving with this tree and the account's owner as program_id
1449                    let owner_bytes = acc.compressed_account.owner.to_bytes();
1450                    let derived = light_compressed_account::address::derive_address(
1451                        pda_pubkey,
1452                        &tree_pubkey,
1453                        &owner_bytes,
1454                    );
1455
1456                    if derived == address {
1457                        return Some(acc);
1458                    }
1459                }
1460            }
1461        }
1462        None
1463    }
1464
1465    /// Find a token compressed account by its PDA pubkey
1466    pub fn find_token_account_by_pda_seed(
1467        &self,
1468        pda_pubkey: &[u8; 32],
1469    ) -> Option<&TokenDataWithMerkleContext> {
1470        // Try each address tree to find an account whose address matches
1471        for address_tree in &self.address_merkle_trees {
1472            let tree_pubkey = address_tree.accounts.merkle_tree.to_bytes();
1473
1474            // For each token compressed account with an address, check if it was derived from this seed
1475            for acc in &self.token_compressed_accounts {
1476                if let Some(address) = acc.compressed_account.compressed_account.address {
1477                    // Try deriving with this tree and the account's owner as program_id
1478                    let owner_bytes = acc.compressed_account.compressed_account.owner.to_bytes();
1479                    let derived = light_compressed_account::address::derive_address(
1480                        pda_pubkey,
1481                        &tree_pubkey,
1482                        &owner_bytes,
1483                    );
1484
1485                    if derived == address {
1486                        return Some(acc);
1487                    }
1488                }
1489            }
1490        }
1491        None
1492    }
1493
1494    /// Get the sequence number for a state merkle tree by its pubkey.
1495    pub fn get_state_tree_seq(&self, tree_pubkey: &Pubkey) -> Option<u64> {
1496        self.state_merkle_trees
1497            .iter()
1498            .find(|tree| tree.accounts.merkle_tree == *tree_pubkey)
1499            .map(|tree| tree.merkle_tree.sequence_number as u64)
1500    }
1501
1502    pub fn add_address_merkle_tree_bundle(
1503        address_merkle_tree_accounts: AddressMerkleTreeAccounts,
1504        // TODO: add config here
1505    ) -> Result<AddressMerkleTreeBundle, IndexerError> {
1506        if address_merkle_tree_accounts.merkle_tree == address_merkle_tree_accounts.queue {
1507            AddressMerkleTreeBundle::new_v2(address_merkle_tree_accounts)
1508        } else {
1509            AddressMerkleTreeBundle::new_v1(address_merkle_tree_accounts)
1510        }
1511    }
1512    #[cfg(feature = "devenv")]
1513    async fn add_address_merkle_tree_v1<R: Rpc>(
1514        &mut self,
1515        rpc: &mut R,
1516        merkle_tree_keypair: &Keypair,
1517        queue_keypair: &Keypair,
1518        owning_program_id: Option<Pubkey>,
1519    ) -> Result<AddressMerkleTreeAccounts, RpcError> {
1520        use crate::accounts::test_keypairs::FORESTER_TEST_KEYPAIR;
1521
1522        let config = if owning_program_id.is_some() {
1523            // We only allow program owned address trees with custom fees.
1524            AddressMerkleTreeConfig {
1525                network_fee: None,
1526                ..AddressMerkleTreeConfig::default()
1527            }
1528        } else {
1529            AddressMerkleTreeConfig::default()
1530        };
1531        create_address_merkle_tree_and_queue_account(
1532            &self.payer,
1533            true,
1534            rpc,
1535            merkle_tree_keypair,
1536            queue_keypair,
1537            owning_program_id,
1538            Some(
1539                Keypair::try_from(FORESTER_TEST_KEYPAIR.as_slice())
1540                    .unwrap()
1541                    .pubkey(),
1542            ), // std forester, we now need to set it.
1543            &config,
1544            &AddressQueueConfig::default(),
1545            0,
1546        )
1547        .await?;
1548
1549        let accounts = <TestIndexer as TestIndexerExtensions>::add_address_merkle_tree_accounts(
1550            self,
1551            merkle_tree_keypair,
1552            queue_keypair,
1553            owning_program_id,
1554        );
1555        Ok(accounts)
1556    }
1557
1558    #[cfg(feature = "devenv")]
1559    async fn add_address_merkle_tree_v2<R: Rpc>(
1560        &mut self,
1561        rpc: &mut R,
1562        merkle_tree_keypair: &Keypair,
1563        queue_keypair: &Keypair,
1564        _owning_program_id: Option<Pubkey>,
1565    ) -> Result<AddressMerkleTreeAccounts, RpcError> {
1566        info!(
1567            "Adding address merkle tree accounts v2 {:?}",
1568            merkle_tree_keypair.pubkey()
1569        );
1570
1571        let params = light_batched_merkle_tree::initialize_address_tree::InitAddressTreeAccountsInstructionData::test_default();
1572
1573        info!(
1574            "Creating batched address merkle tree {:?}",
1575            merkle_tree_keypair.pubkey()
1576        );
1577        create_batch_address_merkle_tree(rpc, &self.payer, merkle_tree_keypair, params).await?;
1578        info!(
1579            "Batched address merkle tree created {:?}",
1580            merkle_tree_keypair.pubkey()
1581        );
1582
1583        let accounts = self.add_address_merkle_tree_accounts(
1584            merkle_tree_keypair,
1585            queue_keypair,
1586            _owning_program_id,
1587        );
1588        Ok(accounts)
1589    }
1590
1591    #[cfg(feature = "devenv")]
1592    pub async fn add_address_merkle_tree<R: Rpc>(
1593        &mut self,
1594        rpc: &mut R,
1595        merkle_tree_keypair: &Keypair,
1596        queue_keypair: &Keypair,
1597        owning_program_id: Option<Pubkey>,
1598        tree_type: TreeType,
1599    ) -> Result<AddressMerkleTreeAccounts, RpcError> {
1600        if tree_type == TreeType::AddressV1 {
1601            self.add_address_merkle_tree_v1(
1602                rpc,
1603                merkle_tree_keypair,
1604                queue_keypair,
1605                owning_program_id,
1606            )
1607            .await
1608        } else if tree_type == TreeType::AddressV2 {
1609            #[cfg(not(feature = "devenv"))]
1610            panic!("Batched address merkle trees require the 'devenv' feature to be enabled");
1611            #[cfg(feature = "devenv")]
1612            self.add_address_merkle_tree_v2(
1613                rpc,
1614                merkle_tree_keypair,
1615                queue_keypair,
1616                owning_program_id,
1617            )
1618            .await
1619        } else {
1620            Err(RpcError::CustomError(format!(
1621                "add_address_merkle_tree: Version not supported, {}. Versions: AddressV1, AddressV2",
1622                tree_type
1623            )))
1624        }
1625    }
1626
1627    #[allow(clippy::too_many_arguments)]
1628    #[cfg(feature = "devenv")]
1629    pub async fn add_state_merkle_tree<R: Rpc>(
1630        &mut self,
1631        rpc: &mut R,
1632        merkle_tree_keypair: &Keypair,
1633        queue_keypair: &Keypair,
1634        cpi_context_keypair: &Keypair,
1635        owning_program_id: Option<Pubkey>,
1636        forester: Option<Pubkey>,
1637        tree_type: TreeType,
1638    ) {
1639        let (rollover_fee, merkle_tree, output_queue_batch_size) = match tree_type {
1640            TreeType::StateV1 => {
1641                create_state_merkle_tree_and_queue_account(
1642                    &self.payer,
1643                    true,
1644                    rpc,
1645                    merkle_tree_keypair,
1646                    queue_keypair,
1647                    Some(cpi_context_keypair),
1648                    owning_program_id,
1649                    forester,
1650                    self.state_merkle_trees.len() as u64,
1651                    &StateMerkleTreeConfig::default(),
1652                    &NullifierQueueConfig::default(),
1653                )
1654                    .await
1655                    .unwrap();
1656                let merkle_tree = Box::new(MerkleTree::<Poseidon>::new_with_history(
1657                    STATE_MERKLE_TREE_HEIGHT as usize,
1658                    STATE_MERKLE_TREE_CANOPY_DEPTH as usize,
1659                    0,
1660                    STATE_MERKLE_TREE_ROOTS as usize,
1661
1662                ));
1663                (FeeConfig::default().state_merkle_tree_rollover as i64,merkle_tree, None)
1664            }
1665            TreeType::StateV2 => {
1666                #[cfg(feature = "devenv")]
1667                {
1668                    let params =  light_batched_merkle_tree::initialize_state_tree::InitStateTreeAccountsInstructionData::test_default();
1669
1670                    create_batched_state_merkle_tree(
1671                        &self.payer,
1672                        true,
1673                        rpc,
1674                        merkle_tree_keypair,
1675                        queue_keypair,
1676                        cpi_context_keypair,
1677                        params,
1678                    ).await.unwrap();
1679                    let merkle_tree = Box::new(MerkleTree::<Poseidon>::new_with_history(
1680                        DEFAULT_BATCH_STATE_TREE_HEIGHT,
1681                        0,
1682                        0,
1683                        DEFAULT_BATCH_ROOT_HISTORY_LEN,
1684
1685                    ));
1686                    (FeeConfig::test_batched().state_merkle_tree_rollover as i64,merkle_tree, Some(params.output_queue_batch_size as usize))
1687                }
1688
1689                #[cfg(not(feature = "devenv"))]
1690                panic!("Batched state merkle trees require the 'devenv' feature to be enabled")
1691            }
1692            _ => panic!(
1693                "add_state_merkle_tree: tree_type not supported, {}. tree_type: 1 concurrent, 2 batched",
1694                tree_type
1695            ),
1696        };
1697        let state_merkle_tree_account = StateMerkleTreeAccounts {
1698            merkle_tree: merkle_tree_keypair.pubkey(),
1699            nullifier_queue: queue_keypair.pubkey(),
1700            cpi_context: cpi_context_keypair.pubkey(),
1701            tree_type,
1702        };
1703
1704        self.state_merkle_trees.push(StateMerkleTreeBundle {
1705            merkle_tree,
1706            accounts: state_merkle_tree_account,
1707            rollover_fee,
1708            tree_type,
1709            output_queue_elements: vec![],
1710            input_leaf_indices: vec![],
1711            num_inserted_batches: 0,
1712            output_queue_batch_size,
1713        });
1714        println!(
1715            "creating Merkle tree bundle {:?}",
1716            self.state_merkle_trees
1717                .iter()
1718                .map(|x| x.accounts.merkle_tree)
1719                .collect::<Vec<_>>()
1720        );
1721    }
1722
1723    /// deserializes an event
1724    /// adds the output_compressed_accounts to the compressed_accounts
1725    /// removes the input_compressed_accounts from the compressed_accounts
1726    /// adds the input_compressed_accounts to the nullified_compressed_accounts
1727    pub fn add_lamport_compressed_accounts(&mut self, slot: u64, event_bytes: Vec<u8>) {
1728        let event_bytes = event_bytes.clone();
1729        let event = PublicTransactionEvent::deserialize(&mut event_bytes.as_slice()).unwrap();
1730        // TODO: map event type
1731        <TestIndexer as TestIndexerExtensions>::add_event_and_compressed_accounts(
1732            self, slot, &event,
1733        );
1734    }
1735
1736    /// returns the compressed sol balance of the owner pubkey
1737    pub fn get_compressed_balance(&self, owner: &Pubkey) -> u64 {
1738        self.compressed_accounts
1739            .iter()
1740            .filter(|x| x.compressed_account.owner.to_bytes() == owner.to_bytes())
1741            .map(|x| x.compressed_account.lamports)
1742            .sum()
1743    }
1744
1745    /// returns the compressed token balance of the owner pubkey for a token by mint
1746    pub fn get_compressed_token_balance(&self, owner: &Pubkey, mint: &Pubkey) -> u64 {
1747        self.token_compressed_accounts
1748            .iter()
1749            .filter(|x| {
1750                x.compressed_account.compressed_account.owner.to_bytes() == owner.to_bytes()
1751                    && x.token_data.mint == *mint
1752            })
1753            .map(|x| x.token_data.amount)
1754            .sum()
1755    }
1756
1757    fn process_v1_compressed_account(
1758        &mut self,
1759        slot: u64,
1760        event: &PublicTransactionEvent,
1761        i: usize,
1762        token_compressed_accounts: &mut Vec<TokenDataWithMerkleContext>,
1763        compressed_accounts: &mut Vec<CompressedAccountWithMerkleContext>,
1764    ) {
1765        let mut input_addresses = vec![];
1766        let mut new_addresses = vec![];
1767        if event.output_compressed_accounts.len() > i {
1768            let compressed_account = &event.output_compressed_accounts[i];
1769            if let Some(address) = compressed_account.compressed_account.address {
1770                if !input_addresses.iter().any(|x| x == &address) {
1771                    new_addresses.push(address);
1772                }
1773            }
1774            let merkle_tree = self.state_merkle_trees.iter().find(|x| {
1775                x.accounts.merkle_tree
1776                    == solana_pubkey::Pubkey::from(
1777                        event.pubkey_array
1778                            [event.output_compressed_accounts[i].merkle_tree_index as usize]
1779                            .to_bytes(),
1780                    )
1781            });
1782            // Check for output queue
1783            let merkle_tree = if let Some(merkle_tree) = merkle_tree {
1784                merkle_tree
1785            } else {
1786                self.state_merkle_trees
1787                    .iter()
1788                    .find(|x| {
1789                        x.accounts.nullifier_queue
1790                            == solana_pubkey::Pubkey::from(
1791                                event.pubkey_array[event.output_compressed_accounts[i]
1792                                    .merkle_tree_index
1793                                    as usize]
1794                                    .to_bytes(),
1795                            )
1796                    })
1797                    .unwrap()
1798            };
1799            let nullifier_queue_pubkey = merkle_tree.accounts.nullifier_queue;
1800            let merkle_tree_pubkey = merkle_tree.accounts.merkle_tree;
1801            // if data is some, try to deserialize token data, if it fails, add to compressed_accounts
1802            // if data is none add to compressed_accounts
1803            // new accounts are inserted in front so that the newest accounts are found first
1804            match compressed_account.compressed_account.data.as_ref() {
1805                Some(data) => {
1806                    // Check for both V1 and V2 token account discriminators
1807                    let is_v1_token = data.discriminator == [2, 0, 0, 0, 0, 0, 0, 0]; // V1 discriminator
1808                    let is_v2_token = data.discriminator == [0, 0, 0, 0, 0, 0, 0, 3]; // V2 discriminator
1809                    let is_v3_token = data.discriminator == [0, 0, 0, 0, 0, 0, 0, 4]; // ShaFlat discriminator
1810
1811                    if compressed_account.compressed_account.owner
1812                        == solana_pubkey::pubkey!("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m")
1813                            .to_bytes()
1814                        && (is_v1_token || is_v2_token || is_v3_token)
1815                    {
1816                        if let Ok(token_data) = TokenData::deserialize(&mut data.data.as_slice()) {
1817                            let token_account = TokenDataWithMerkleContext {
1818                                token_data,
1819                                compressed_account: CompressedAccountWithMerkleContext {
1820                                    compressed_account: compressed_account
1821                                        .compressed_account
1822                                        .clone(),
1823                                    merkle_context: MerkleContext {
1824                                        leaf_index: event.output_leaf_indices[i],
1825                                        merkle_tree_pubkey: merkle_tree_pubkey.into(),
1826                                        queue_pubkey: nullifier_queue_pubkey.into(),
1827                                        prove_by_index: false,
1828                                        tree_type: merkle_tree.tree_type,
1829                                    },
1830                                },
1831                            };
1832                            token_compressed_accounts.push(token_account.clone());
1833                            self.token_compressed_accounts.insert(0, token_account);
1834                        }
1835                    } else {
1836                        let compressed_account = CompressedAccountWithMerkleContext {
1837                            compressed_account: compressed_account.compressed_account.clone(),
1838                            merkle_context: MerkleContext {
1839                                leaf_index: event.output_leaf_indices[i],
1840                                merkle_tree_pubkey: merkle_tree_pubkey.into(),
1841                                queue_pubkey: nullifier_queue_pubkey.into(),
1842                                prove_by_index: false,
1843                                tree_type: merkle_tree.tree_type,
1844                            },
1845                        };
1846                        compressed_accounts.push(compressed_account.clone());
1847                        self.compressed_accounts.insert(0, compressed_account);
1848                    }
1849                }
1850                None => {
1851                    let compressed_account = CompressedAccountWithMerkleContext {
1852                        compressed_account: compressed_account.compressed_account.clone(),
1853                        merkle_context: MerkleContext {
1854                            leaf_index: event.output_leaf_indices[i],
1855                            merkle_tree_pubkey: merkle_tree_pubkey.into(),
1856                            queue_pubkey: nullifier_queue_pubkey.into(),
1857                            prove_by_index: false,
1858                            tree_type: merkle_tree.tree_type,
1859                        },
1860                    };
1861                    compressed_accounts.push(compressed_account.clone());
1862                    self.compressed_accounts.insert(0, compressed_account);
1863                }
1864            };
1865            let merkle_tree = &mut self.state_merkle_trees.iter_mut().find(|x| {
1866                x.accounts.merkle_tree
1867                    == solana_pubkey::Pubkey::from(
1868                        event.pubkey_array
1869                            [event.output_compressed_accounts[i].merkle_tree_index as usize]
1870                            .to_bytes(),
1871                    )
1872            });
1873            if merkle_tree.is_some() {
1874                let merkle_tree = merkle_tree.as_mut().unwrap();
1875                let leaf_hash = compressed_account
1876                    .compressed_account
1877                    .hash(
1878                        &event.pubkey_array
1879                            [event.output_compressed_accounts[i].merkle_tree_index as usize],
1880                        &event.output_leaf_indices[i],
1881                        false,
1882                    )
1883                    .unwrap();
1884                merkle_tree
1885                    .merkle_tree
1886                    .append(&leaf_hash)
1887                    .expect("insert failed");
1888            } else {
1889                let merkle_tree = &mut self
1890                    .state_merkle_trees
1891                    .iter_mut()
1892                    .find(|x| {
1893                        x.accounts.nullifier_queue
1894                            == solana_pubkey::Pubkey::from(
1895                                event.pubkey_array[event.output_compressed_accounts[i]
1896                                    .merkle_tree_index
1897                                    as usize]
1898                                    .to_bytes(),
1899                            )
1900                    })
1901                    .unwrap();
1902
1903                merkle_tree.output_queue_elements.push((
1904                    event.output_compressed_account_hashes[i],
1905                    event.output_leaf_indices[i].into(),
1906                ));
1907            }
1908        }
1909        if event.input_compressed_account_hashes.len() > i {
1910            let tx_hash: [u8; 32] = create_tx_hash(
1911                &event.input_compressed_account_hashes,
1912                &event.output_compressed_account_hashes,
1913                slot,
1914            )
1915            .unwrap();
1916            let hash = event.input_compressed_account_hashes[i];
1917            let index = self
1918                .compressed_accounts
1919                .iter()
1920                .position(|x| x.hash().unwrap() == hash);
1921            let (leaf_index, merkle_tree_pubkey) = if let Some(index) = index {
1922                self.nullified_compressed_accounts
1923                    .push(self.compressed_accounts[index].clone());
1924                let leaf_index = self.compressed_accounts[index].merkle_context.leaf_index;
1925                let merkle_tree_pubkey = self.compressed_accounts[index]
1926                    .merkle_context
1927                    .merkle_tree_pubkey;
1928                if let Some(address) = self.compressed_accounts[index].compressed_account.address {
1929                    input_addresses.push(address);
1930                }
1931                self.compressed_accounts.remove(index);
1932                (Some(leaf_index), Some(merkle_tree_pubkey))
1933            } else if let Some(index) = self
1934                .token_compressed_accounts
1935                .iter()
1936                .position(|x| x.compressed_account.hash().unwrap() == hash)
1937            {
1938                self.token_nullified_compressed_accounts
1939                    .push(self.token_compressed_accounts[index].clone());
1940                let leaf_index = self.token_compressed_accounts[index]
1941                    .compressed_account
1942                    .merkle_context
1943                    .leaf_index;
1944                let merkle_tree_pubkey = self.token_compressed_accounts[index]
1945                    .compressed_account
1946                    .merkle_context
1947                    .merkle_tree_pubkey;
1948                self.token_compressed_accounts.remove(index);
1949                (Some(leaf_index), Some(merkle_tree_pubkey))
1950            } else {
1951                (None, None)
1952            };
1953            if let Some(leaf_index) = leaf_index {
1954                let merkle_tree_pubkey = merkle_tree_pubkey.unwrap();
1955                let bundle =
1956                    &mut <TestIndexer as TestIndexerExtensions>::get_state_merkle_trees_mut(self)
1957                        .iter_mut()
1958                        .find(|x| {
1959                            x.accounts.merkle_tree
1960                                == solana_pubkey::Pubkey::from(merkle_tree_pubkey.to_bytes())
1961                        })
1962                        .unwrap();
1963                // Store leaf indices of input accounts for batched trees
1964                if bundle.tree_type == TreeType::StateV2 {
1965                    let leaf_hash = event.input_compressed_account_hashes[i];
1966                    bundle.input_leaf_indices.push(LeafIndexInfo {
1967                        leaf_index,
1968                        leaf: leaf_hash,
1969                        tx_hash,
1970                    });
1971                }
1972            } else {
1973                println!("Test indexer didn't find input compressed accounts to nullify");
1974            }
1975        }
1976        // checks whether there are addresses in outputs which don't exist in inputs.
1977        // if so check pubkey_array for the first address Merkle tree and append to the bundles queue elements.
1978        // Note:
1979        // - creating addresses in multiple address Merkle trees in one tx is not supported
1980        // TODO: reimplement this is not a good solution
1981        // - take addresses and address Merkle tree pubkeys from cpi to account compression program
1982        if !new_addresses.is_empty() {
1983            for pubkey in event.pubkey_array.iter() {
1984                if let Some((_, address_merkle_tree)) = self
1985                    .address_merkle_trees
1986                    .iter_mut()
1987                    .enumerate()
1988                    .find(|(_, x)| {
1989                        x.accounts.merkle_tree == solana_pubkey::Pubkey::from(pubkey.to_bytes())
1990                    })
1991                {
1992                    address_merkle_tree
1993                        .queue_elements
1994                        .append(&mut new_addresses);
1995                }
1996            }
1997        }
1998    }
1999
2000    async fn _get_multiple_new_address_proofs(
2001        &self,
2002        merkle_tree_pubkey: [u8; 32],
2003        addresses: Vec<[u8; 32]>,
2004        full: bool,
2005    ) -> Result<Vec<NewAddressProofWithContext>, IndexerError> {
2006        let mut proofs: Vec<NewAddressProofWithContext> = Vec::new();
2007
2008        for address in addresses.iter() {
2009            info!("Getting new address proof for {:?}", address);
2010            let pubkey = Pubkey::from(merkle_tree_pubkey);
2011            let address_tree_bundle = self
2012                .address_merkle_trees
2013                .iter()
2014                .find(|x| x.accounts.merkle_tree == pubkey)
2015                .unwrap();
2016
2017            let address_biguint = BigUint::from_bytes_be(address.as_slice());
2018            let (old_low_address, _old_low_address_next_value) =
2019                address_tree_bundle.find_low_element_for_nonexistent(&address_biguint)?;
2020            let address_bundle = address_tree_bundle
2021                .new_element_with_low_element_index(old_low_address.index, &address_biguint)?;
2022
2023            let (old_low_address, old_low_address_next_value) =
2024                address_tree_bundle.find_low_element_for_nonexistent(&address_biguint)?;
2025
2026            // Get the Merkle proof for updating low element.
2027            let low_address_proof =
2028                address_tree_bundle.get_proof_of_leaf(old_low_address.index, full)?;
2029
2030            let low_address_index: u64 = old_low_address.index as u64;
2031            let low_address_value: [u8; 32] =
2032                bigint_to_be_bytes_array(&old_low_address.value).unwrap();
2033            let low_address_next_index: u64 = old_low_address.next_index as u64;
2034            let low_address_next_value: [u8; 32] =
2035                bigint_to_be_bytes_array(&old_low_address_next_value).unwrap();
2036            let proof = NewAddressProofWithContext {
2037                merkle_tree: Pubkey::new_from_array(merkle_tree_pubkey),
2038                low_address_index,
2039                low_address_value,
2040                low_address_next_index,
2041                low_address_next_value,
2042                low_address_proof,
2043                root: address_tree_bundle.root(),
2044                root_seq: address_tree_bundle.sequence_number(),
2045                new_low_element: Some(address_bundle.new_low_element),
2046                new_element: Some(address_bundle.new_element),
2047                new_element_next_value: Some(address_bundle.new_element_next_value),
2048            };
2049            proofs.push(proof);
2050        }
2051        Ok(proofs)
2052    }
2053}
2054
2055impl TestIndexer {
2056    async fn process_inclusion_proofs(
2057        &self,
2058        merkle_tree_pubkeys: &[Pubkey],
2059        accounts: &[[u8; 32]],
2060    ) -> Result<
2061        (
2062            Option<BatchInclusionJsonStruct>,
2063            Option<BatchInclusionJsonStructLegacy>,
2064            Vec<AccountProofInputs>,
2065        ),
2066        IndexerError,
2067    > {
2068        let mut inclusion_proofs = Vec::new();
2069        let mut account_proof_inputs = Vec::new();
2070        let mut height = 0;
2071        let mut queues = vec![];
2072        let mut cpi_contextes = vec![];
2073        let mut tree_types = vec![];
2074        // Collect all proofs first before any await points
2075        let proof_data: Vec<_> = accounts
2076            .iter()
2077            .zip(merkle_tree_pubkeys.iter())
2078            .map(|(account, &pubkey)| {
2079                let bundle = self
2080                    .state_merkle_trees
2081                    .iter()
2082                    .find(|x| {
2083                        x.accounts.merkle_tree == pubkey || x.accounts.nullifier_queue == pubkey
2084                    })
2085                    .unwrap();
2086                println!("accounts {:?}", bundle.accounts);
2087                let merkle_tree = &bundle.merkle_tree;
2088                queues.push(bundle.accounts.nullifier_queue);
2089                cpi_contextes.push(bundle.accounts.cpi_context);
2090                tree_types.push(bundle.tree_type);
2091                let leaf_index = merkle_tree.get_leaf_index(account).unwrap();
2092                let proof = merkle_tree.get_proof_of_leaf(leaf_index, true).unwrap();
2093
2094                // Convert proof to owned data that implements Send
2095                let proof: Vec<BigInt> = proof.iter().map(|x| BigInt::from_be_bytes(x)).collect();
2096
2097                if height == 0 {
2098                    height = merkle_tree.height;
2099                } else {
2100                    assert_eq!(height, merkle_tree.height);
2101                }
2102                let root_index = if bundle.tree_type == TreeType::StateV1 {
2103                    merkle_tree.get_history_root_index().unwrap()
2104                } else {
2105                    merkle_tree.get_history_root_index_v2().unwrap()
2106                };
2107
2108                Ok((leaf_index, proof, merkle_tree.root(), root_index))
2109            })
2110            .collect::<Result<_, IndexerError>>()?;
2111
2112        // Now handle the async operations with the collected data
2113        for (i, (leaf_index, proof, merkle_root, root_index)) in proof_data.into_iter().enumerate()
2114        {
2115            inclusion_proofs.push(InclusionMerkleProofInputs {
2116                root: BigInt::from_be_bytes(merkle_root.as_slice()),
2117                leaf: BigInt::from_be_bytes(&accounts[i]),
2118                path_index: BigInt::from_be_bytes(leaf_index.to_be_bytes().as_slice()),
2119                path_elements: proof,
2120            });
2121
2122            account_proof_inputs.push(AccountProofInputs {
2123                root_index: RootIndex::new_some(root_index),
2124                root: merkle_root,
2125                leaf_index: leaf_index as u64,
2126                hash: accounts[i],
2127                tree_info: light_client::indexer::TreeInfo {
2128                    cpi_context: Some(cpi_contextes[i]),
2129                    next_tree_info: None,
2130                    queue: queues[i],
2131                    tree: merkle_tree_pubkeys[i],
2132                    tree_type: tree_types[i],
2133                },
2134            });
2135        }
2136
2137        let (batch_inclusion_proof_inputs, legacy) = if height == DEFAULT_BATCH_STATE_TREE_HEIGHT {
2138            let inclusion_proof_inputs =
2139                InclusionProofInputs::new(inclusion_proofs.as_slice()).unwrap();
2140            (
2141                Some(BatchInclusionJsonStruct::from_inclusion_proof_inputs(
2142                    &inclusion_proof_inputs,
2143                )),
2144                None,
2145            )
2146        } else if height == STATE_MERKLE_TREE_HEIGHT as usize {
2147            let inclusion_proof_inputs = InclusionProofInputsLegacy(inclusion_proofs.as_slice());
2148            (
2149                None,
2150                Some(BatchInclusionJsonStructLegacy::from_inclusion_proof_inputs(
2151                    &inclusion_proof_inputs,
2152                )),
2153            )
2154        } else {
2155            return Err(IndexerError::CustomError(
2156                "Unsupported tree height".to_string(),
2157            ));
2158        };
2159
2160        Ok((batch_inclusion_proof_inputs, legacy, account_proof_inputs))
2161    }
2162
2163    async fn process_non_inclusion_proofs(
2164        &self,
2165        address_merkle_tree_pubkeys: &[Pubkey],
2166        addresses: Vec<[u8; 32]>,
2167    ) -> Result<
2168        (
2169            Option<BatchNonInclusionJsonStruct>,
2170            Option<BatchNonInclusionJsonStructLegacy>,
2171            Vec<AddressProofInputs>,
2172        ),
2173        IndexerError,
2174    > {
2175        let mut non_inclusion_proofs = Vec::new();
2176        let mut address_root_indices = Vec::new();
2177        let mut tree_heights = Vec::new();
2178        for (i, address) in addresses.iter().enumerate() {
2179            let address_tree = self
2180                .address_merkle_trees
2181                .iter()
2182                .find(|x| x.accounts.merkle_tree == address_merkle_tree_pubkeys[i])
2183                .unwrap();
2184            tree_heights.push(address_tree.height());
2185
2186            let proof_inputs = address_tree.get_non_inclusion_proof_inputs(address)?;
2187            non_inclusion_proofs.push(proof_inputs);
2188
2189            let (root_index, root, tree_type) = match &address_tree.merkle_tree {
2190                super::address_tree::IndexedMerkleTreeVersion::V1(tree) => (
2191                    tree.merkle_tree.get_history_root_index().unwrap() + 1,
2192                    tree.merkle_tree.root(),
2193                    TreeType::AddressV1,
2194                ),
2195                super::address_tree::IndexedMerkleTreeVersion::V2(tree) => (
2196                    tree.merkle_tree.get_history_root_index_v2().unwrap(),
2197                    tree.merkle_tree.root(),
2198                    TreeType::AddressV2,
2199                ),
2200            };
2201            address_root_indices.push(AddressProofInputs {
2202                root_index,
2203                root,
2204                address: *address,
2205                tree_info: light_client::indexer::TreeInfo {
2206                    cpi_context: None,
2207                    next_tree_info: None,
2208                    queue: address_tree.accounts.queue,
2209                    tree: address_tree.accounts.merkle_tree,
2210                    tree_type,
2211                },
2212            });
2213        }
2214        // if tree heights are not the same, panic
2215        if tree_heights.iter().any(|&x| x != tree_heights[0]) {
2216            return Err(IndexerError::CustomError(format!(
2217                "All address merkle trees must have the same height {:?}",
2218                tree_heights
2219            )));
2220        }
2221        let (batch_non_inclusion_proof_inputs, batch_non_inclusion_proof_inputs_legacy) =
2222            if tree_heights[0] == 26 {
2223                let non_inclusion_proof_inputs =
2224                    NonInclusionProofInputsLegacy::new(non_inclusion_proofs.as_slice());
2225                (
2226                    None,
2227                    Some(
2228                        BatchNonInclusionJsonStructLegacy::from_non_inclusion_proof_inputs(
2229                            &non_inclusion_proof_inputs,
2230                        ),
2231                    ),
2232                )
2233            } else if tree_heights[0] == 40 {
2234                let non_inclusion_proof_inputs =
2235                    NonInclusionProofInputs::new(non_inclusion_proofs.as_slice()).unwrap();
2236                (
2237                    Some(
2238                        BatchNonInclusionJsonStruct::from_non_inclusion_proof_inputs(
2239                            &non_inclusion_proof_inputs,
2240                        ),
2241                    ),
2242                    None,
2243                )
2244            } else {
2245                return Err(IndexerError::CustomError(
2246                    "Unsupported tree height".to_string(),
2247                ));
2248            };
2249        Ok((
2250            batch_non_inclusion_proof_inputs,
2251            batch_non_inclusion_proof_inputs_legacy,
2252            address_root_indices,
2253        ))
2254    }
2255}
2256
2257impl TestIndexer {
2258    async fn _get_validity_proof_v1_implementation(
2259        &self,
2260        hashes: Vec<[u8; 32]>,
2261        new_addresses_with_trees: Vec<AddressWithTree>,
2262    ) -> Result<ValidityProofWithContext, IndexerError> {
2263        let mut state_merkle_tree_pubkeys = Vec::new();
2264
2265        for hash in hashes.iter() {
2266            let account = self.get_compressed_account_by_hash(*hash, None).await?;
2267            let account_data = account.value.ok_or(IndexerError::AccountNotFound)?;
2268            state_merkle_tree_pubkeys.push(account_data.tree_info.tree);
2269        }
2270
2271        let state_merkle_tree_pubkeys = if state_merkle_tree_pubkeys.is_empty() {
2272            None
2273        } else {
2274            Some(state_merkle_tree_pubkeys)
2275        };
2276        let hashes = if hashes.is_empty() {
2277            None
2278        } else {
2279            Some(hashes)
2280        };
2281        let new_addresses = if new_addresses_with_trees.is_empty() {
2282            None
2283        } else {
2284            Some(
2285                new_addresses_with_trees
2286                    .iter()
2287                    .map(|x| x.address)
2288                    .collect::<Vec<[u8; 32]>>(),
2289            )
2290        };
2291        let address_merkle_tree_pubkeys = if new_addresses_with_trees.is_empty() {
2292            None
2293        } else {
2294            Some(
2295                new_addresses_with_trees
2296                    .iter()
2297                    .map(|x| x.tree)
2298                    .collect::<Vec<Pubkey>>(),
2299            )
2300        };
2301
2302        {
2303            let compressed_accounts = hashes;
2304            if compressed_accounts.is_some() && compressed_accounts.as_ref().unwrap().len() > 8 {
2305                return Err(IndexerError::CustomError(format!(
2306                    "compressed_accounts must be of length <= 8, got {}",
2307                    compressed_accounts.unwrap().len()
2308                )));
2309            }
2310            if new_addresses.is_some() && new_addresses.as_ref().unwrap().len() > 8 {
2311                return Err(IndexerError::CustomError(format!(
2312                    "new_addresses must be of length <= 8, got {}",
2313                    new_addresses.unwrap().len()
2314                )));
2315            }
2316            let client = Client::new();
2317            let (account_proof_inputs, address_proof_inputs, json_payload) =
2318                match (compressed_accounts, new_addresses) {
2319                    (Some(accounts), None) => {
2320                        let (payload, payload_legacy, indices) = self
2321                            .process_inclusion_proofs(
2322                                &state_merkle_tree_pubkeys.unwrap(),
2323                                &accounts,
2324                            )
2325                            .await?;
2326                        if let Some(payload) = payload {
2327                            (indices, Vec::new(), payload.to_string())
2328                        } else {
2329                            (indices, Vec::new(), payload_legacy.unwrap().to_string())
2330                        }
2331                    }
2332                    (None, Some(addresses)) => {
2333                        let (payload, payload_legacy, indices) = self
2334                            .process_non_inclusion_proofs(
2335                                address_merkle_tree_pubkeys.unwrap().as_slice(),
2336                                addresses,
2337                            )
2338                            .await?;
2339                        let payload_string = if let Some(payload) = payload {
2340                            payload.to_string()
2341                        } else {
2342                            payload_legacy.unwrap().to_string()
2343                        };
2344                        (Vec::new(), indices, payload_string)
2345                    }
2346                    (Some(accounts), Some(addresses)) => {
2347                        let (inclusion_payload, inclusion_payload_legacy, inclusion_indices) = self
2348                            .process_inclusion_proofs(
2349                                &state_merkle_tree_pubkeys.unwrap(),
2350                                &accounts,
2351                            )
2352                            .await?;
2353
2354                        let (
2355                            non_inclusion_payload,
2356                            non_inclusion_payload_legacy,
2357                            non_inclusion_indices,
2358                        ) = self
2359                            .process_non_inclusion_proofs(
2360                                address_merkle_tree_pubkeys.unwrap().as_slice(),
2361                                addresses,
2362                            )
2363                            .await?;
2364
2365                        // Validate that we're not mixing v1 and v2 tree versions
2366                        match (inclusion_payload.is_some(), non_inclusion_payload.is_some()) {
2367                            (true, true) | (false, false) => {
2368                                // Both v2 or both v1 - OK, proceed
2369                            }
2370                            (false, true) => {
2371                                // v1 state trees (height 26) with v2 address trees (height 40)
2372                                return Err(IndexerError::MixedTreeVersions {
2373                                    state_version: "v1 (state tree height 26)".to_string(),
2374                                    address_version: "v2 (address tree height 40)".to_string(),
2375                                });
2376                            }
2377                            (true, false) => {
2378                                // v2 state trees with v1 address trees (height 26)
2379                                return Err(IndexerError::MixedTreeVersions {
2380                                    state_version: "v2 (state tree)".to_string(),
2381                                    address_version: "v1 (address tree height 26)".to_string(),
2382                                });
2383                            }
2384                        }
2385
2386                        let json_payload = if let Some(non_inclusion_payload) =
2387                            non_inclusion_payload
2388                        {
2389                            let public_input_hash = BigInt::from_bytes_be(
2390                                num_bigint::Sign::Plus,
2391                                &create_hash_chain_from_slice(&[
2392                                    bigint_to_u8_32(
2393                                        &string_to_big_int(
2394                                            &inclusion_payload.as_ref().unwrap().public_input_hash,
2395                                        )
2396                                        .unwrap(),
2397                                    )
2398                                    .unwrap(),
2399                                    bigint_to_u8_32(
2400                                        &string_to_big_int(
2401                                            &non_inclusion_payload.public_input_hash,
2402                                        )
2403                                        .unwrap(),
2404                                    )
2405                                    .unwrap(),
2406                                ])
2407                                .unwrap(),
2408                            );
2409
2410                            CombinedJsonStruct {
2411                                circuit_type: ProofType::Combined.to_string(),
2412                                state_tree_height: DEFAULT_BATCH_STATE_TREE_HEIGHT as u32,
2413                                address_tree_height: DEFAULT_BATCH_ADDRESS_TREE_HEIGHT as u32,
2414                                public_input_hash: big_int_to_string(&public_input_hash),
2415                                inclusion: inclusion_payload.unwrap().inputs,
2416                                non_inclusion: non_inclusion_payload.inputs,
2417                            }
2418                            .to_string()
2419                        } else if let Some(non_inclusion_payload) = non_inclusion_payload_legacy {
2420                            CombinedJsonStructLegacy {
2421                                circuit_type: ProofType::Combined.to_string(),
2422                                state_tree_height: 26,
2423                                address_tree_height: 26,
2424                                inclusion: inclusion_payload_legacy.unwrap().inputs,
2425                                non_inclusion: non_inclusion_payload.inputs,
2426                            }
2427                            .to_string()
2428                        } else {
2429                            panic!("Unsupported tree height")
2430                        };
2431                        (inclusion_indices, non_inclusion_indices, json_payload)
2432                    }
2433                    _ => {
2434                        panic!(
2435                            "At least one of compressed_accounts or new_addresses must be provided"
2436                        )
2437                    }
2438                };
2439
2440            let mut retries = 3;
2441            while retries > 0 {
2442                let response_result = client
2443                    .post(format!("{}{}", SERVER_ADDRESS, PROVE_PATH))
2444                    .header("Content-Type", "text/plain; charset=utf-8")
2445                    .body(json_payload.clone())
2446                    .send()
2447                    .await;
2448                if let Ok(response_result) = response_result {
2449                    if response_result.status().is_success() {
2450                        let body = response_result.text().await.unwrap();
2451                        let proof_json = deserialize_gnark_proof_json(&body).unwrap();
2452                        let (proof_a, proof_b, proof_c) = proof_from_json_struct(proof_json);
2453                        let (proof_a, proof_b, proof_c) =
2454                            compress_proof(&proof_a, &proof_b, &proof_c);
2455                        return Ok(ValidityProofWithContext {
2456                            accounts: account_proof_inputs,
2457                            addresses: address_proof_inputs,
2458                            proof: CompressedProof {
2459                                a: proof_a,
2460                                b: proof_b,
2461                                c: proof_c,
2462                            }
2463                            .into(),
2464                        });
2465                    }
2466                } else {
2467                    println!("Error: {:#?}", response_result);
2468                    tokio::time::sleep(Duration::from_secs(5)).await;
2469                    retries -= 1;
2470                }
2471            }
2472            Err(IndexerError::CustomError(
2473                "Failed to get proof from server".to_string(),
2474            ))
2475        }
2476    }
2477}