light_batched_merkle_tree/
queue.rs

1use std::ops::{Deref, DerefMut};
2
3use aligned_sized::aligned_sized;
4use light_account_checks::{
5    checks::{check_account_info, set_discriminator},
6    discriminator::{Discriminator, DISCRIMINATOR_LEN},
7    AccountInfoTrait,
8};
9use light_compressed_account::{
10    hash_to_bn254_field_size_be, pubkey::Pubkey, QueueType, OUTPUT_STATE_QUEUE_TYPE_V2,
11};
12use light_merkle_tree_metadata::{errors::MerkleTreeMetadataError, queue::QueueMetadata};
13use light_zero_copy::{errors::ZeroCopyError, vec::ZeroCopyVecU64};
14use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref};
15
16// Import the feature-gated types from lib.rs
17use super::batch::BatchState;
18use crate::{
19    batch::Batch,
20    constants::{ACCOUNT_COMPRESSION_PROGRAM_ID, NUM_BATCHES},
21    errors::BatchedMerkleTreeError,
22    queue_batch_metadata::QueueBatches,
23    BorshDeserialize, BorshSerialize,
24};
25
26#[repr(C)]
27#[derive(
28    BorshDeserialize,
29    BorshSerialize,
30    Debug,
31    PartialEq,
32    Default,
33    Clone,
34    Copy,
35    KnownLayout,
36    Immutable,
37    FromBytes,
38    IntoBytes,
39)]
40#[aligned_sized(anchor)]
41pub struct BatchedQueueMetadata {
42    pub metadata: QueueMetadata,
43    pub batch_metadata: QueueBatches,
44    /// Maximum number of leaves that can fit in the tree, calculated as 2^height.
45    /// For example, a tree with height 3 can hold up to 8 leaves.
46    pub tree_capacity: u64,
47    pub hashed_merkle_tree_pubkey: [u8; 32],
48    pub hashed_queue_pubkey: [u8; 32],
49}
50
51impl BatchedQueueMetadata {
52    pub fn init(
53        &mut self,
54        meta_data: QueueMetadata,
55        batch_size: u64,
56        zkp_batch_size: u64,
57        bloom_filter_capacity: u64,
58        num_iters: u64,
59        queue_pubkey: &Pubkey,
60    ) -> Result<(), BatchedMerkleTreeError> {
61        self.metadata = meta_data;
62        self.batch_metadata.init(batch_size, zkp_batch_size)?;
63        self.batch_metadata.bloom_filter_capacity = bloom_filter_capacity;
64        for (i, batches) in self.batch_metadata.batches.iter_mut().enumerate() {
65            *batches = Batch::new(
66                num_iters,
67                bloom_filter_capacity,
68                batch_size,
69                zkp_batch_size,
70                batch_size * (i as u64),
71            );
72        }
73
74        // Precompute Merkle tree pubkey hash for use in system program.
75        // The compressed account hash depends on the Merkle tree pubkey and leaf index.
76        // Poseidon hashes required input size < bn254 field size.
77        // To map 256bit pubkeys to < 254bit field size, we hash Pubkeys
78        // and truncate the hash to 31 bytes/248 bits.
79        self.hashed_merkle_tree_pubkey =
80            hash_to_bn254_field_size_be(&meta_data.associated_merkle_tree.to_bytes());
81        self.hashed_queue_pubkey = hash_to_bn254_field_size_be(&queue_pubkey.to_bytes());
82        Ok(())
83    }
84}
85
86/// Batched queue zero copy account.
87/// Used for output queues in light protocol.
88/// Output queues store compressed account hashes,
89/// to be appended to the associated batched Merkle tree
90/// in batches with a zero-knowledge proof (zkp),
91/// ie. it stores hashes and commits these to hash chains.
92/// Each hash chain is used as public input for
93/// a batch append zkp.
94///
95/// An output queue is configured with:
96/// 1. 2 batches
97/// 2. 2 value vecs (one for each batch)
98///    value vec length = batch size
99/// 3. 2 hash chain vecs (one for each batch)
100///    hash chain store length = batch size /zkp batch size
101///
102/// Default config:
103/// - 50,000 batch size
104/// - 500 zkp batch size
105///
106/// Initialization:
107/// - an output queue is initialized
108///   in combination with a state Merkle tree
109/// - `init_batched_state_merkle_tree_from_account_info`
110///
111/// For deserialization use:
112/// - in program:   `output_from_account_info`
113/// - in client:    `output_from_bytes`
114///
115/// To insert a value the account compression program uses:
116/// - `insert_into_current_batch`
117///
118/// A compressed account can be spent or read
119/// while in the output queue.
120///
121/// To spend, the account compression program uses:
122/// - check_leaf_index_could_exist_in_batches in combination with
123///   `prove_inclusion_by_index_and_zero_out_leaf`
124///
125/// To read, light the system program uses:
126/// - `prove_inclusion_by_index`
127#[derive(Debug, PartialEq)]
128pub struct BatchedQueueAccount<'a> {
129    pubkey: Pubkey,
130    metadata: Ref<&'a mut [u8], BatchedQueueMetadata>,
131    pub value_vecs: [ZeroCopyVecU64<'a, [u8; 32]>; 2],
132    pub hash_chain_stores: [ZeroCopyVecU64<'a, [u8; 32]>; 2],
133}
134
135impl Discriminator for BatchedQueueAccount<'_> {
136    const LIGHT_DISCRIMINATOR: [u8; 8] = *b"queueacc";
137    const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = b"queueacc";
138}
139
140impl<'a> BatchedQueueAccount<'a> {
141    /// Deserialize an output BatchedQueueAccount from account info.
142    /// Should be used in solana programs.
143    /// Checks that:
144    /// 1. the program owner is the light account compression program,
145    /// 2. discriminator,
146    /// 3. queue type is output queue type.
147    pub fn output_from_account_info<A: AccountInfoTrait>(
148        account_info: &A,
149    ) -> Result<BatchedQueueAccount<'a>, BatchedMerkleTreeError> {
150        Self::from_account_info::<OUTPUT_STATE_QUEUE_TYPE_V2, A>(
151            &Pubkey::new_from_array(ACCOUNT_COMPRESSION_PROGRAM_ID),
152            account_info,
153        )
154    }
155
156    /// Deserialize a BatchedQueueAccount from account info.
157    /// Should be used in solana programs.
158    /// Checks the program owner, discriminator and queue type.
159    fn from_account_info<const QUEUE_TYPE: u64, A: AccountInfoTrait>(
160        program_id: &Pubkey,
161        account_info: &A,
162    ) -> Result<BatchedQueueAccount<'a>, BatchedMerkleTreeError> {
163        check_account_info::<Self, A>(&program_id.to_bytes(), account_info)?;
164        let account_data = &mut account_info.try_borrow_mut_data()?;
165        // Necessary to convince the borrow checker.
166        let account_data: &'a mut [u8] = unsafe {
167            std::slice::from_raw_parts_mut(account_data.as_mut_ptr(), account_data.len())
168        };
169        Self::from_bytes::<QUEUE_TYPE>(account_data, account_info.key().into())
170    }
171
172    /// Deserialize a BatchedQueueAccount from bytes.
173    /// Should only be used in client.
174    /// Checks the discriminator and queue type.
175    #[cfg(not(target_os = "solana"))]
176    pub fn output_from_bytes(
177        account_data: &'a mut [u8],
178    ) -> Result<BatchedQueueAccount<'a>, BatchedMerkleTreeError> {
179        light_account_checks::checks::check_discriminator::<BatchedQueueAccount>(account_data)?;
180        Self::from_bytes::<OUTPUT_STATE_QUEUE_TYPE_V2>(account_data, Pubkey::default())
181    }
182
183    fn from_bytes<const QUEUE_TYPE: u64>(
184        account_data: &'a mut [u8],
185        pubkey: Pubkey,
186    ) -> Result<BatchedQueueAccount<'a>, BatchedMerkleTreeError> {
187        let (_discriminator, account_data) = account_data.split_at_mut(DISCRIMINATOR_LEN);
188        let (metadata, account_data) =
189            Ref::<&'a mut [u8], BatchedQueueMetadata>::from_prefix(account_data)
190                .map_err(ZeroCopyError::from)?;
191
192        if metadata.metadata.queue_type != QUEUE_TYPE {
193            return Err(MerkleTreeMetadataError::InvalidQueueType.into());
194        }
195
196        let (value_vec0, account_data) = ZeroCopyVecU64::from_bytes_at(account_data)?;
197        let (value_vec1, account_data) = ZeroCopyVecU64::from_bytes_at(account_data)?;
198
199        let (hash_chain_store0, account_data) = ZeroCopyVecU64::from_bytes_at(account_data)?;
200        let hash_chain_store1 = ZeroCopyVecU64::from_bytes(account_data)?;
201
202        Ok(BatchedQueueAccount {
203            pubkey,
204            metadata,
205            value_vecs: [value_vec0, value_vec1],
206            hash_chain_stores: [hash_chain_store0, hash_chain_store1],
207        })
208    }
209
210    pub fn init(
211        account_data: &'a mut [u8],
212        metadata: QueueMetadata,
213        output_queue_batch_size: u64,
214        output_queue_zkp_batch_size: u64,
215        num_iters: u64,
216        bloom_filter_capacity: u64,
217        pubkey: Pubkey,
218    ) -> Result<BatchedQueueAccount<'a>, BatchedMerkleTreeError> {
219        let account_data_len = account_data.len();
220        let (discriminator, account_data) = account_data.split_at_mut(DISCRIMINATOR_LEN);
221        set_discriminator::<Self>(discriminator)?;
222
223        let (mut account_metadata, account_data) =
224            Ref::<&mut [u8], BatchedQueueMetadata>::from_prefix(account_data)
225                .map_err(ZeroCopyError::from)?;
226
227        account_metadata.init(
228            metadata,
229            output_queue_batch_size,
230            output_queue_zkp_batch_size,
231            bloom_filter_capacity,
232            num_iters,
233            &pubkey,
234        )?;
235
236        if account_data_len
237            != account_metadata
238                .batch_metadata
239                .queue_account_size(account_metadata.metadata.queue_type)?
240        {
241            #[cfg(feature = "solana")]
242            solana_msg::msg!("account_data.len() {:?}", account_data_len);
243            #[cfg(feature = "solana")]
244            solana_msg::msg!(
245                "queue_account_size {:?}",
246                account_metadata
247                    .batch_metadata
248                    .queue_account_size(account_metadata.metadata.queue_type)?
249            );
250            return Err(ZeroCopyError::Size.into());
251        }
252
253        let value_vec_capacity = account_metadata.batch_metadata.batch_size;
254        let hash_chain_capacity = account_metadata.batch_metadata.get_num_zkp_batches();
255        let (value_vecs_0, account_data) =
256            ZeroCopyVecU64::new_at(value_vec_capacity, account_data)?;
257        let (value_vecs_1, account_data) =
258            ZeroCopyVecU64::new_at(value_vec_capacity, account_data)?;
259        let (hash_chain_0, account_data) =
260            ZeroCopyVecU64::new_at(hash_chain_capacity, account_data)?;
261        let hash_chain_1 = ZeroCopyVecU64::new(hash_chain_capacity, account_data)?;
262        Ok(BatchedQueueAccount {
263            pubkey,
264            metadata: account_metadata,
265            value_vecs: [value_vecs_0, value_vecs_1],
266            hash_chain_stores: [hash_chain_0, hash_chain_1],
267        })
268    }
269
270    /// Insert a value into the current batch
271    /// of this output queue account.
272    /// 1. insert value into a value vec and hash chain store.
273    /// 2. Increment next_index.
274    pub fn insert_into_current_batch(
275        &mut self,
276        hash_chain_value: &[u8; 32],
277        current_slot: &u64,
278    ) -> Result<(), BatchedMerkleTreeError> {
279        let current_index = self.batch_metadata.next_index;
280
281        insert_into_current_queue_batch(
282            self.metadata.metadata.queue_type,
283            &mut self.metadata.batch_metadata,
284            &mut self.value_vecs,
285            &mut [],
286            &mut self.hash_chain_stores,
287            hash_chain_value,
288            None,
289            Some(current_index),
290            current_slot,
291        )?;
292        self.metadata.batch_metadata.next_index += 1;
293
294        Ok(())
295    }
296
297    /// Proves inclusion of leaf index if it exists in one of the batches.
298    /// 1. Iterate over all batches
299    /// 2. Check if leaf index could exist in the batch.
300    ///    2.1 If yes, check whether value at index is equal to hash_chain_value.
301    ///    Throw error if not.
302    /// 3. Return true if leaf index exists in one of the batches.
303    ///
304    /// Note, this method does not fail but returns `false`
305    ///     if the leaf index is out of range for any batch.
306    pub fn prove_inclusion_by_index(
307        &mut self,
308        leaf_index: u64,
309        hash_chain_value: &[u8; 32],
310    ) -> Result<bool, BatchedMerkleTreeError> {
311        self.internal_prove_inclusion_by_index::<false>(leaf_index, hash_chain_value)
312    }
313
314    fn internal_prove_inclusion_by_index<const ZERO_OUT: bool>(
315        &mut self,
316        leaf_index: u64,
317        hash_chain_value: &[u8; 32],
318    ) -> Result<bool, BatchedMerkleTreeError> {
319        if leaf_index >= self.batch_metadata.next_index {
320            return Err(BatchedMerkleTreeError::InvalidIndex);
321        }
322        for (batch_index, batch) in self.batch_metadata.batches.iter().enumerate() {
323            if batch.leaf_index_exists(leaf_index) {
324                let index = batch.get_value_index_in_batch(leaf_index)?;
325                let element = self.value_vecs[batch_index]
326                    .get_mut(index as usize)
327                    .ok_or(BatchedMerkleTreeError::InclusionProofByIndexFailed)?;
328
329                if *element == *hash_chain_value {
330                    if ZERO_OUT {
331                        *element = [0u8; 32];
332                    }
333                    return Ok(true);
334                } else {
335                    #[cfg(target_os = "solana")]
336                    {
337                        solana_msg::msg!(
338                            "Index found but value doesn't match leaf_index {} compressed account hash: {:?} expected compressed account hash {:?}. (If the expected element is [0u8;32] it was already spent. Other possibly causes, data hash, discriminator, leaf index, or Merkle tree mismatch.)",
339                            leaf_index,
340                            hash_chain_value,*element
341                        );
342                    }
343                    return Err(BatchedMerkleTreeError::InclusionProofByIndexFailed);
344                }
345            }
346        }
347        Ok(false)
348    }
349
350    /// Zero out a leaf by index if it exists in the queues hash_chain_value vec.
351    /// If prove_by_index is true fail if leaf is not found.
352    pub fn prove_inclusion_by_index_and_zero_out_leaf(
353        &mut self,
354        leaf_index: u64,
355        hash_chain_value: &[u8; 32],
356        prove_by_index: bool,
357    ) -> Result<(), BatchedMerkleTreeError> {
358        // Always check and zero out an existing value.
359        let is_proven_by_index =
360            self.internal_prove_inclusion_by_index::<true>(leaf_index, hash_chain_value)?;
361        if is_proven_by_index {
362            return Ok(());
363        }
364        // If no value is found and a check is not enforced return ok.
365        if prove_by_index {
366            #[cfg(target_os = "solana")]
367            {
368                solana_msg::msg!(
369                   "leaf_index {} compressed account hash: {:?}. Possibly causes, leaf index, or Merkle tree mismatch.)",
370                    leaf_index,
371                    hash_chain_value
372                );
373            }
374            Err(BatchedMerkleTreeError::InclusionProofByIndexFailed)
375        } else {
376            Ok(())
377        }
378    }
379
380    pub fn get_metadata(&self) -> &BatchedQueueMetadata {
381        &self.metadata
382    }
383
384    pub fn get_metadata_mut(&mut self) -> &mut BatchedQueueMetadata {
385        &mut self.metadata
386    }
387
388    /// Returns the number of elements inserted in the current batch.
389    /// If current batch state is inserted, returns 0.
390    pub fn get_num_inserted_in_current_batch(&self) -> u64 {
391        let current_batch = self.batch_metadata.currently_processing_batch_index as usize;
392        if self.batch_metadata.batches[current_batch].get_state() == BatchState::Inserted {
393            0
394        } else {
395            self.batch_metadata.batches[current_batch].get_num_inserted_elements()
396        }
397    }
398
399    /// Returns true if the pubkey is the associated Merkle tree of the queue.
400    pub fn is_associated(&self, pubkey: &Pubkey) -> bool {
401        self.metadata.metadata.associated_merkle_tree == *pubkey
402    }
403
404    /// Check if the pubkey is the associated Merkle tree of the queue.
405    pub fn check_is_associated(&self, pubkey: &Pubkey) -> Result<(), BatchedMerkleTreeError> {
406        if !self.is_associated(pubkey) {
407            return Err(MerkleTreeMetadataError::MerkleTreeAndQueueNotAssociated.into());
408        }
409        Ok(())
410    }
411
412    /// Returns true if the tree is full.
413    pub fn tree_is_full(&self) -> bool {
414        self.tree_capacity == self.batch_metadata.next_index
415    }
416
417    /// Check if the tree is full.
418    pub fn check_tree_is_full(&self) -> Result<(), BatchedMerkleTreeError> {
419        if self.tree_is_full() {
420            return Err(BatchedMerkleTreeError::TreeIsFull);
421        }
422        Ok(())
423    }
424
425    pub fn pubkey(&self) -> &Pubkey {
426        &self.pubkey
427    }
428}
429
430impl Deref for BatchedQueueAccount<'_> {
431    type Target = BatchedQueueMetadata;
432
433    fn deref(&self) -> &Self::Target {
434        &self.metadata
435    }
436}
437
438impl DerefMut for BatchedQueueAccount<'_> {
439    fn deref_mut(&mut self) -> &mut Self::Target {
440        &mut self.metadata
441    }
442}
443
444/// Insert a value into the current batch.
445/// - Input & address queues: Insert into bloom filter & hash chain.
446/// - Output queue: Insert into value vec & hash chain.
447///
448/// Steps:
449/// 1. Check if the current batch is ready.
450///    1.1. If the current batch is inserted, clear the batch.
451/// 2. Insert value into the current batch.
452/// 3. If batch is full, increment currently_processing_batch_index.
453#[allow(clippy::too_many_arguments)]
454#[allow(clippy::type_complexity)]
455pub(crate) fn insert_into_current_queue_batch(
456    queue_type: u64,
457    batch_metadata: &mut QueueBatches,
458    value_vecs: &mut [ZeroCopyVecU64<[u8; 32]>],
459    bloom_filter_stores: &mut [&mut [u8]],
460    hash_chain_stores: &mut [ZeroCopyVecU64<[u8; 32]>],
461    hash_chain_value: &[u8; 32],
462    bloom_filter_value: Option<&[u8; 32]>,
463    current_index: Option<u64>,
464    current_slot: &u64,
465) -> Result<(), BatchedMerkleTreeError> {
466    let batch_index = batch_metadata.currently_processing_batch_index as usize;
467    let mut value_store = value_vecs.get_mut(batch_index);
468    let mut hash_chain_stores = hash_chain_stores.get_mut(batch_index);
469    let current_batch = batch_metadata.get_current_batch_mut();
470    // 1. Check that the current batch is ready (BatchState::Fill).
471    //      1.1. If the current batch is inserted, clear the batch.
472    {
473        let clear_batch = current_batch.get_state() == BatchState::Inserted;
474        if current_batch.get_state() == BatchState::Fill {
475            // Do nothing, checking most common case first.
476        } else if clear_batch {
477            // Clear the batch if it is inserted.
478
479            // If a batch contains a bloom filter it must be zeroed by a forester.
480            if queue_type != QueueType::OutputStateV2 as u64
481                && !current_batch.bloom_filter_is_zeroed()
482            {
483                return Err(BatchedMerkleTreeError::BloomFilterNotZeroed);
484            }
485            if let Some(value_store) = value_store.as_mut() {
486                (*value_store).clear();
487            }
488            if let Some(hash_chain_stores) = hash_chain_stores.as_mut() {
489                (*hash_chain_stores).clear();
490            }
491            // Advance the state to fill and reset the number of inserted elements.
492            // If Some(current_index) set it as start index.
493            // Reset, sequence number, root index, bloom filter zeroed, num_inserted_zkps
494            // start_slot, start_slot_is_set.
495            current_batch.advance_state_to_fill(current_index)?;
496        } else {
497            // We expect to insert into the current batch.
498            #[cfg(feature = "solana")]
499            for batch in batch_metadata.batches.iter() {
500                solana_msg::msg!("batch {:?}", batch);
501            }
502            return Err(BatchedMerkleTreeError::BatchNotReady);
503        }
504    }
505
506    // 2. Insert value into the current batch.
507    let queue_type = QueueType::from(queue_type);
508    match queue_type {
509        QueueType::InputStateV2 | QueueType::AddressV2 => current_batch.insert(
510            bloom_filter_value.unwrap(),
511            hash_chain_value,
512            bloom_filter_stores,
513            hash_chain_stores.as_mut().unwrap(),
514            batch_index,
515            current_slot,
516        ),
517        QueueType::OutputStateV2 => current_batch.store_and_hash_value(
518            hash_chain_value,
519            value_store.unwrap(),
520            hash_chain_stores.unwrap(),
521            current_slot,
522        ),
523        _ => Err(MerkleTreeMetadataError::InvalidQueueType.into()),
524    }?;
525
526    // 3. If batch is full, increment currently_processing_batch_index.
527    batch_metadata.increment_currently_processing_batch_index_if_full();
528
529    Ok(())
530}
531
532#[inline(always)]
533pub(crate) fn deserialize_bloom_filter_stores(
534    bloom_filter_capacity: usize,
535    account_data: &mut [u8],
536) -> ([&mut [u8]; 2], &mut [u8]) {
537    let (slice_1, account_data) = account_data.split_at_mut(bloom_filter_capacity);
538    let (slice_2, account_data) = account_data.split_at_mut(bloom_filter_capacity);
539    ([slice_1, slice_2], account_data)
540}
541
542pub fn get_output_queue_account_size(batch_size: u64, zkp_batch_size: u64) -> usize {
543    let metadata = BatchedQueueMetadata {
544        metadata: QueueMetadata::default(),
545        batch_metadata: QueueBatches {
546            num_batches: NUM_BATCHES as u64,
547            batch_size,
548            zkp_batch_size,
549            ..Default::default()
550        },
551        ..Default::default()
552    };
553    metadata
554        .batch_metadata
555        .queue_account_size(QueueType::OutputStateV2 as u64)
556        .unwrap()
557}
558
559#[cfg(feature = "test-only")]
560pub mod test_utils {
561    use super::*;
562    use crate::{
563        constants::{NUM_BATCHES, TEST_DEFAULT_BATCH_SIZE, TEST_DEFAULT_ZKP_BATCH_SIZE},
564        initialize_state_tree::InitStateTreeAccountsInstructionData,
565    };
566    pub fn get_output_queue_account_size_default() -> usize {
567        let batch_metadata = BatchedQueueMetadata {
568            metadata: QueueMetadata::default(),
569            batch_metadata: QueueBatches {
570                num_batches: NUM_BATCHES as u64,
571                batch_size: TEST_DEFAULT_BATCH_SIZE,
572                zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
573                ..Default::default()
574            },
575            ..Default::default()
576        };
577        batch_metadata
578            .batch_metadata
579            .queue_account_size(QueueType::OutputStateV2 as u64)
580            .unwrap()
581    }
582
583    pub fn get_output_queue_account_size_from_params(
584        ix_data: InitStateTreeAccountsInstructionData,
585    ) -> usize {
586        let metadata = BatchedQueueMetadata {
587            metadata: QueueMetadata::default(),
588            batch_metadata: QueueBatches {
589                num_batches: NUM_BATCHES as u64,
590                batch_size: ix_data.output_queue_batch_size,
591                zkp_batch_size: ix_data.output_queue_zkp_batch_size,
592                ..Default::default()
593            },
594            ..Default::default()
595        };
596        metadata
597            .batch_metadata
598            .queue_account_size(QueueType::OutputStateV2 as u64)
599            .unwrap()
600    }
601
602    #[allow(clippy::too_many_arguments)]
603    pub fn assert_queue_inited(
604        batch_metadata: QueueBatches,
605        ref_batch_metadata: QueueBatches,
606        queue_type: u64,
607        value_vecs: &mut [ZeroCopyVecU64<'_, [u8; 32]>],
608    ) {
609        assert_eq!(
610            batch_metadata, ref_batch_metadata,
611            "batch_metadata mismatch"
612        );
613
614        if queue_type == QueueType::OutputStateV2 as u64 {
615            assert_eq!(value_vecs.len(), NUM_BATCHES, "value_vecs mismatch");
616        } else {
617            assert_eq!(value_vecs.len(), 0, "value_vecs mismatch");
618        }
619        for vec in value_vecs.iter() {
620            assert_eq!(
621                vec.capacity(),
622                batch_metadata.batch_size as usize,
623                "batch_size mismatch"
624            );
625            assert_eq!(vec.len(), 0, "batch_size mismatch");
626        }
627    }
628    pub fn assert_queue_zero_copy_inited(
629        account_data: &mut [u8],
630        ref_account: BatchedQueueMetadata,
631    ) {
632        let mut account = BatchedQueueAccount::output_from_bytes(account_data)
633            .expect("from_bytes_unchecked_mut failed");
634        let batch_metadata = account.batch_metadata;
635        let queue_type = account.metadata.metadata.queue_type;
636        assert_eq!(
637            account.metadata.metadata, ref_account.metadata,
638            "metadata mismatch"
639        );
640        assert_queue_inited(
641            batch_metadata,
642            ref_account.batch_metadata,
643            queue_type,
644            &mut account.value_vecs,
645        );
646    }
647}
648
649#[cfg(feature = "test-only")]
650#[test]
651fn test_from_bytes_invalid_tree_type() {
652    use crate::queue::test_utils::get_output_queue_account_size_default;
653    let mut account_data = vec![0u8; get_output_queue_account_size_default()];
654    let account = BatchedQueueAccount::from_bytes::<6>(&mut account_data, Pubkey::default());
655    assert_eq!(
656        account.unwrap_err(),
657        MerkleTreeMetadataError::InvalidQueueType.into()
658    );
659}
660
661#[test]
662fn test_batched_queue_metadata_init() {
663    let mut metadata = BatchedQueueMetadata::default();
664    let mt_pubkey = Pubkey::new_unique();
665    let queue_metadata = QueueMetadata {
666        associated_merkle_tree: mt_pubkey,
667        ..Default::default()
668    };
669    let batch_size = 4;
670    let zkp_batch_size = 2;
671    let bloom_filter_capacity = 10;
672    let num_iters = 5;
673    let queue_pubkey = Pubkey::new_unique();
674
675    let result = metadata.init(
676        queue_metadata,
677        batch_size,
678        zkp_batch_size,
679        bloom_filter_capacity,
680        num_iters,
681        &queue_pubkey,
682    );
683
684    assert!(result.is_ok());
685    assert_eq!(metadata.metadata, queue_metadata);
686    assert_eq!(
687        metadata.batch_metadata.bloom_filter_capacity,
688        bloom_filter_capacity
689    );
690    for (i, batch) in metadata.batch_metadata.batches.iter().enumerate() {
691        assert_eq!(batch.num_iters, num_iters);
692        assert_eq!(batch.bloom_filter_capacity, bloom_filter_capacity);
693        assert_eq!(batch.batch_size, batch_size);
694        assert_eq!(batch.zkp_batch_size, zkp_batch_size);
695        assert_eq!(batch.start_index, batch_size * (i as u64));
696    }
697    let hashed_merkle_tree_pubkey = hash_to_bn254_field_size_be(&mt_pubkey.to_bytes());
698    let hashed_queue_pubkey = hash_to_bn254_field_size_be(&queue_pubkey.to_bytes());
699    assert_eq!(
700        metadata.hashed_merkle_tree_pubkey,
701        hashed_merkle_tree_pubkey
702    );
703    assert_eq!(metadata.hashed_queue_pubkey, hashed_queue_pubkey);
704}
705
706#[test]
707fn test_check_is_associated() {
708    let mut account_data = vec![0u8; 1000];
709    let mut queue_metadata = QueueMetadata::default();
710    let associated_merkle_tree = Pubkey::new_unique();
711    queue_metadata.associated_merkle_tree = associated_merkle_tree;
712    queue_metadata.queue_type = QueueType::OutputStateV2 as u64;
713    let batch_size = 4;
714    let zkp_batch_size = 2;
715    let bloom_filter_capacity = 0;
716    let num_iters = 0;
717    let account = BatchedQueueAccount::init(
718        &mut account_data,
719        queue_metadata,
720        batch_size,
721        zkp_batch_size,
722        num_iters,
723        bloom_filter_capacity,
724        Pubkey::new_unique(),
725    )
726    .unwrap();
727    // 1. Functional
728    {
729        account
730            .check_is_associated(&associated_merkle_tree)
731            .unwrap();
732        assert!(account.is_associated(&associated_merkle_tree));
733    }
734    // 2. Failing
735    {
736        let other_merkle_tree = Pubkey::new_unique();
737        assert_eq!(
738            account.check_is_associated(&other_merkle_tree),
739            Err(MerkleTreeMetadataError::MerkleTreeAndQueueNotAssociated.into())
740        );
741        assert!(!account.is_associated(&other_merkle_tree));
742    }
743}
744
745#[test]
746fn test_pubkey() {
747    let mut account_data = vec![0u8; 1000];
748    let mut queue_metadata = QueueMetadata::default();
749    let associated_merkle_tree = Pubkey::new_unique();
750    queue_metadata.associated_merkle_tree = associated_merkle_tree;
751    queue_metadata.queue_type = QueueType::OutputStateV2 as u64;
752    let batch_size = 4;
753    let zkp_batch_size = 2;
754    let bloom_filter_capacity = 0;
755    let num_iters = 0;
756    let pubkey = Pubkey::new_unique();
757    let account = BatchedQueueAccount::init(
758        &mut account_data,
759        queue_metadata,
760        batch_size,
761        zkp_batch_size,
762        num_iters,
763        bloom_filter_capacity,
764        pubkey,
765    )
766    .unwrap();
767    assert_eq!(*account.pubkey(), pubkey);
768}