light_batched_merkle_tree/
initialize_state_tree.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2use light_account_checks::{checks::check_account_balance_is_rent_exempt, AccountInfoTrait};
3use light_compressed_account::{pubkey::Pubkey, QueueType, TreeType};
4use light_merkle_tree_metadata::{
5    access::AccessMetadata, fee::compute_rollover_fee, merkle_tree::MerkleTreeMetadata,
6    queue::QueueMetadata, rollover::RolloverMetadata,
7};
8
9use crate::{
10    constants::{
11        DEFAULT_BATCH_SIZE, DEFAULT_BATCH_STATE_TREE_HEIGHT, DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
12        DEFAULT_ZKP_BATCH_SIZE,
13    },
14    errors::BatchedMerkleTreeError,
15    merkle_tree::{get_merkle_tree_account_size, BatchedMerkleTreeAccount},
16    queue::{get_output_queue_account_size, BatchedQueueAccount},
17};
18
19#[repr(C)]
20#[derive(Debug, Clone, Copy, BorshDeserialize, BorshSerialize, PartialEq)]
21pub struct InitStateTreeAccountsInstructionData {
22    /// Unchecked identifier of the state tree.
23    pub index: u64,
24    /// Program owning the tree, enforced in the system program.
25    pub program_owner: Option<Pubkey>,
26    /// Optional forester pubkey for trees not forested
27    /// by light foresters enforced in registry program.
28    pub forester: Option<Pubkey>,
29    pub additional_bytes: u64,
30    pub input_queue_batch_size: u64,
31    pub output_queue_batch_size: u64,
32    pub input_queue_zkp_batch_size: u64,
33    pub output_queue_zkp_batch_size: u64,
34    pub bloom_filter_num_iters: u64,
35    pub bloom_filter_capacity: u64,
36    pub root_history_capacity: u32,
37    pub network_fee: Option<u64>,
38    pub rollover_threshold: Option<u64>,
39    /// Placeholder unimplemented.
40    pub close_threshold: Option<u64>,
41    pub height: u32,
42}
43
44impl Default for InitStateTreeAccountsInstructionData {
45    fn default() -> Self {
46        Self {
47            index: 0,
48            program_owner: None,
49            forester: None,
50            additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
51            bloom_filter_num_iters: 3,
52            input_queue_batch_size: DEFAULT_BATCH_SIZE,
53            output_queue_batch_size: DEFAULT_BATCH_SIZE,
54            input_queue_zkp_batch_size: DEFAULT_ZKP_BATCH_SIZE,
55            output_queue_zkp_batch_size: DEFAULT_ZKP_BATCH_SIZE,
56            height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
57            root_history_capacity: (DEFAULT_BATCH_SIZE / DEFAULT_ZKP_BATCH_SIZE * 2) as u32,
58            bloom_filter_capacity: DEFAULT_BATCH_SIZE * 8,
59            network_fee: Some(5000),
60            rollover_threshold: Some(95),
61            close_threshold: None,
62        }
63    }
64}
65
66/// Initializes the state Merkle tree and output queue accounts.
67/// 1. Check rent exemption and that accounts are initialized with the correct size.
68/// 2. Initialize the output queue and state Merkle tree accounts.
69pub fn init_batched_state_merkle_tree_from_account_info<A: AccountInfoTrait>(
70    params: InitStateTreeAccountsInstructionData,
71    owner: Pubkey,
72    merkle_tree_account_info: &A,
73    queue_account_info: &A,
74    additional_bytes_rent: u64,
75) -> Result<(), BatchedMerkleTreeError> {
76    // 1. Check rent exemption and that accounts are initialized with the correct size.
77    let queue_rent;
78    let merkle_tree_rent;
79    {
80        let queue_account_size = get_output_queue_account_size(
81            params.output_queue_batch_size,
82            params.output_queue_zkp_batch_size,
83        );
84        let mt_account_size = get_merkle_tree_account_size(
85            params.input_queue_batch_size,
86            params.bloom_filter_capacity,
87            params.input_queue_zkp_batch_size,
88            params.root_history_capacity,
89            params.height,
90        );
91
92        queue_rent = check_account_balance_is_rent_exempt(queue_account_info, queue_account_size)?;
93
94        merkle_tree_rent =
95            check_account_balance_is_rent_exempt(merkle_tree_account_info, mt_account_size)?;
96    }
97
98    // 2. Initialize the output queue and state Merkle tree accounts.
99    let queue_data = &mut queue_account_info.try_borrow_mut_data()?;
100    let mt_data = &mut merkle_tree_account_info.try_borrow_mut_data()?;
101
102    init_batched_state_merkle_tree_accounts(
103        owner,
104        params,
105        queue_data,
106        queue_account_info.key().into(),
107        queue_rent,
108        mt_data,
109        merkle_tree_account_info.key().into(),
110        merkle_tree_rent,
111        additional_bytes_rent,
112    )?;
113    Ok(())
114}
115
116#[allow(clippy::too_many_arguments)]
117pub fn init_batched_state_merkle_tree_accounts<'a>(
118    owner: Pubkey,
119    params: InitStateTreeAccountsInstructionData,
120    output_queue_account_data: &mut [u8],
121    output_queue_pubkey: Pubkey,
122    queue_rent: u64,
123    mt_account_data: &'a mut [u8],
124    mt_pubkey: Pubkey,
125    merkle_tree_rent: u64,
126    additional_bytes_rent: u64,
127) -> Result<BatchedMerkleTreeAccount<'a>, BatchedMerkleTreeError> {
128    let height = params.height;
129    // Output queue
130    {
131        let rollover_fee = match params.rollover_threshold {
132            Some(rollover_threshold) => {
133                let rent = merkle_tree_rent + additional_bytes_rent + queue_rent;
134                compute_rollover_fee(rollover_threshold, height, rent)?
135            }
136            None => 0,
137        };
138
139        #[cfg(feature = "solana")]
140        solana_msg::msg!(" Output queue rollover_fee: {}", rollover_fee);
141        let metadata = QueueMetadata {
142            next_queue: Pubkey::default(),
143            access_metadata: AccessMetadata::new(owner, params.program_owner, params.forester),
144            rollover_metadata: RolloverMetadata::new(
145                params.index,
146                rollover_fee,
147                params.rollover_threshold,
148                params.network_fee.unwrap_or_default(),
149                params.close_threshold,
150                Some(params.additional_bytes),
151            ),
152            queue_type: QueueType::OutputStateV2 as u64,
153            associated_merkle_tree: mt_pubkey,
154        };
155
156        BatchedQueueAccount::init(
157            output_queue_account_data,
158            metadata,
159            params.output_queue_batch_size,
160            params.output_queue_zkp_batch_size,
161            // Output queues have no bloom filter.
162            0,
163            0,
164            output_queue_pubkey,
165        )?;
166    }
167    let metadata = MerkleTreeMetadata {
168        next_merkle_tree: Pubkey::default(),
169        access_metadata: AccessMetadata::new(owner, params.program_owner, params.forester),
170        rollover_metadata: RolloverMetadata::new(
171            params.index,
172            // The complete rollover fee is charged when creating an output
173            // compressed account by inserting it into the output queue.
174            0,
175            params.rollover_threshold,
176            params.network_fee.unwrap_or_default(),
177            params.close_threshold,
178            None,
179        ),
180        associated_queue: output_queue_pubkey,
181    };
182
183    // The state Merkle tree account includes the input queue.
184    // A nullifier is inserted when compressed state is spent.
185    // Spending compressed state requires proving its inclusion,
186    // which needs a root from the tree account.
187    BatchedMerkleTreeAccount::init(
188        mt_account_data,
189        &mt_pubkey,
190        metadata,
191        params.root_history_capacity,
192        params.input_queue_batch_size,
193        params.input_queue_zkp_batch_size,
194        height,
195        params.bloom_filter_num_iters,
196        params.bloom_filter_capacity,
197        TreeType::StateV2,
198    )
199}
200
201pub fn validate_batched_tree_params(params: InitStateTreeAccountsInstructionData) {
202    assert!(params.input_queue_batch_size > 0);
203    assert!(params.output_queue_batch_size > 0);
204    assert_eq!(
205        params.input_queue_batch_size % params.input_queue_zkp_batch_size,
206        0,
207        "Input queue batch size must divisible by input_queue_zkp_batch_size."
208    );
209    assert_eq!(
210        params.output_queue_batch_size % params.output_queue_zkp_batch_size,
211        0,
212        "Output queue batch size must divisible by output_queue_zkp_batch_size."
213    );
214    assert!(
215        match_circuit_size(params.input_queue_zkp_batch_size),
216        "Zkp batch size not supported. Supported 1, 10, 100, 500, 1000"
217    );
218    assert!(
219        match_circuit_size(params.output_queue_zkp_batch_size),
220        "Zkp batch size not supported. Supported 1, 10, 100, 500, 1000"
221    );
222
223    assert!(params.bloom_filter_num_iters > 0);
224    assert!(params.bloom_filter_capacity >= params.input_queue_batch_size * 8);
225    assert_eq!(
226        params.bloom_filter_capacity % 8,
227        0,
228        "Bloom filter capacity must be divisible by 8."
229    );
230    assert!(params.bloom_filter_capacity > 0);
231    assert!(params.root_history_capacity > 0);
232    assert!(params.input_queue_batch_size > 0);
233    assert_eq!(params.close_threshold, None);
234    assert_eq!(params.height, DEFAULT_BATCH_STATE_TREE_HEIGHT);
235}
236
237pub fn match_circuit_size(size: u64) -> bool {
238    matches!(size, 10 | 100 | 250 | 500 | 1000)
239}
240#[cfg(feature = "test-only")]
241pub mod test_utils {
242    use light_compressed_account::hash_to_bn254_field_size_be;
243
244    pub use super::InitStateTreeAccountsInstructionData;
245    use super::*;
246    use crate::{
247        constants::{
248            STATE_BLOOM_FILTER_CAPACITY, STATE_BLOOM_FILTER_NUM_HASHES, TEST_DEFAULT_BATCH_SIZE,
249            TEST_DEFAULT_ZKP_BATCH_SIZE,
250        },
251        queue::{test_utils::assert_queue_inited, BatchedQueueMetadata},
252        queue_batch_metadata::QueueBatches,
253    };
254
255    impl InitStateTreeAccountsInstructionData {
256        pub fn test_default() -> Self {
257            Self {
258                index: 0,
259                program_owner: None,
260                forester: None,
261                additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
262                bloom_filter_num_iters: 3,
263                input_queue_batch_size: TEST_DEFAULT_BATCH_SIZE,
264                output_queue_batch_size: TEST_DEFAULT_BATCH_SIZE,
265                input_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
266                output_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
267                height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
268                root_history_capacity: 20,
269                bloom_filter_capacity: 20_000 * 8,
270                network_fee: Some(5000),
271                rollover_threshold: Some(95),
272                close_threshold: None,
273            }
274        }
275
276        pub fn e2e_test_default() -> Self {
277            Self {
278                index: 0,
279                program_owner: None,
280                forester: None,
281                additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
282                bloom_filter_num_iters: 3,
283                input_queue_batch_size: 500,
284                output_queue_batch_size: 500,
285                input_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
286                output_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
287                height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
288                root_history_capacity: 20,
289                bloom_filter_capacity: 20_000 * 8,
290                network_fee: Some(5000),
291                rollover_threshold: Some(95),
292                close_threshold: None,
293            }
294        }
295
296        pub fn testnet_default() -> Self {
297            Self {
298                index: 0,
299                program_owner: None,
300                forester: None,
301                additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
302                bloom_filter_num_iters: STATE_BLOOM_FILTER_NUM_HASHES,
303                input_queue_batch_size: 15000,
304                output_queue_batch_size: 15000,
305                input_queue_zkp_batch_size: DEFAULT_ZKP_BATCH_SIZE,
306                output_queue_zkp_batch_size: DEFAULT_ZKP_BATCH_SIZE,
307                height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
308                root_history_capacity: 20,
309                bloom_filter_capacity: STATE_BLOOM_FILTER_CAPACITY,
310                network_fee: Some(5000),
311                rollover_threshold: Some(95),
312                close_threshold: None,
313            }
314        }
315    }
316
317    pub fn get_state_merkle_tree_account_size_from_params(
318        params: InitStateTreeAccountsInstructionData,
319    ) -> usize {
320        crate::merkle_tree::get_merkle_tree_account_size(
321            params.input_queue_batch_size,
322            params.bloom_filter_capacity,
323            params.input_queue_zkp_batch_size,
324            params.root_history_capacity,
325            params.height,
326        )
327    }
328
329    pub fn assert_state_mt_zero_copy_initialized(
330        account_data: &mut [u8],
331        ref_account: crate::merkle_tree_metadata::BatchedMerkleTreeMetadata,
332        pubkey: &Pubkey,
333    ) {
334        let account = BatchedMerkleTreeAccount::state_from_bytes(account_data, pubkey)
335            .expect("from_bytes_unchecked_mut failed");
336        _assert_mt_zero_copy_initialized::<{ light_compressed_account::STATE_MERKLE_TREE_TYPE_V2 }>(
337            account,
338            ref_account,
339            TreeType::StateV2 as u64,
340        );
341    }
342
343    pub fn assert_address_mt_zero_copy_initialized(
344        account_data: &mut [u8],
345        ref_account: crate::merkle_tree_metadata::BatchedMerkleTreeMetadata,
346        pubkey: &Pubkey,
347    ) {
348        use crate::merkle_tree::BatchedMerkleTreeAccount;
349
350        let account = BatchedMerkleTreeAccount::address_from_bytes(account_data, pubkey)
351            .expect("from_bytes_unchecked_mut failed");
352        _assert_mt_zero_copy_initialized::<{ light_compressed_account::STATE_MERKLE_TREE_TYPE_V2 }>(
353            account,
354            ref_account,
355            TreeType::AddressV1 as u64,
356        );
357    }
358
359    fn _assert_mt_zero_copy_initialized<const TREE_TYPE: u64>(
360        account: BatchedMerkleTreeAccount,
361        ref_account: crate::merkle_tree_metadata::BatchedMerkleTreeMetadata,
362        tree_type: u64,
363    ) {
364        use light_compressed_account::QueueType;
365        use light_hasher::Hasher;
366
367        let queue = account.queue_batches;
368        let ref_queue = ref_account.queue_batches;
369        assert_eq!(*account, ref_account, "metadata mismatch");
370
371        assert_eq!(
372            account.root_history.capacity(),
373            ref_account.root_history_capacity as usize,
374            "root_history_capacity mismatch"
375        );
376        if tree_type == TreeType::StateV2 as u64 {
377            assert_eq!(
378                *account.root_history.get(0).unwrap(),
379                light_hasher::Poseidon::zero_bytes()[ref_account.height as usize],
380                "root_history not initialized"
381            );
382        }
383        if tree_type == TreeType::AddressV2 as u64 {
384            assert_eq!(
385                *account.root_history.get(0).unwrap(),
386                crate::constants::ADDRESS_TREE_INIT_ROOT_40,
387                "root_history not initialized"
388            );
389        }
390        assert_eq!(
391            account.hash_chain_stores[0].capacity(),
392            ref_account.queue_batches.get_num_zkp_batches() as usize,
393            "hash_chain_store mismatch"
394        );
395
396        let queue_type = if tree_type == TreeType::StateV2 as u64 {
397            QueueType::InputStateV2 as u64
398        } else {
399            QueueType::AddressV2 as u64
400        };
401        assert_queue_inited(queue, ref_queue, queue_type, &mut []);
402    }
403
404    #[derive(Debug, Clone, Copy)]
405    #[repr(C)]
406    pub struct CreateOutputQueueParams {
407        pub owner: Pubkey,
408        pub program_owner: Option<Pubkey>,
409        pub forester: Option<Pubkey>,
410        pub rollover_threshold: Option<u64>,
411        pub index: u64,
412        pub batch_size: u64,
413        pub zkp_batch_size: u64,
414        pub additional_bytes: u64,
415        pub rent: u64,
416        pub associated_merkle_tree: Pubkey,
417        pub queue_pubkey: Pubkey,
418        pub height: u32,
419        pub network_fee: u64,
420    }
421
422    impl CreateOutputQueueParams {
423        pub fn from(
424            params: InitStateTreeAccountsInstructionData,
425            owner: Pubkey,
426            rent: u64,
427            associated_merkle_tree: Pubkey,
428            queue_pubkey: Pubkey,
429        ) -> Self {
430            Self {
431                owner,
432                program_owner: params.program_owner,
433                forester: params.forester,
434                rollover_threshold: params.rollover_threshold,
435                index: params.index,
436                batch_size: params.output_queue_batch_size,
437                zkp_batch_size: params.output_queue_zkp_batch_size,
438                additional_bytes: params.additional_bytes,
439                rent,
440                associated_merkle_tree,
441                height: params.height,
442                network_fee: params.network_fee.unwrap_or_default(),
443                queue_pubkey,
444            }
445        }
446    }
447
448    pub fn create_output_queue_account(params: CreateOutputQueueParams) -> BatchedQueueMetadata {
449        let rollover_fee: u64 = match params.rollover_threshold {
450            Some(rollover_threshold) => {
451                compute_rollover_fee(rollover_threshold, params.height, params.rent).unwrap()
452            }
453            None => 0,
454        };
455        let metadata = QueueMetadata {
456            next_queue: Pubkey::default(),
457            access_metadata: AccessMetadata {
458                owner: params.owner,
459                program_owner: params.program_owner.unwrap_or_default(),
460                forester: params.forester.unwrap_or_default(),
461            },
462            rollover_metadata: RolloverMetadata {
463                close_threshold: u64::MAX,
464                index: params.index,
465                rolledover_slot: u64::MAX,
466                rollover_threshold: params.rollover_threshold.unwrap_or(u64::MAX),
467                rollover_fee,
468                network_fee: params.network_fee,
469                additional_bytes: params.additional_bytes,
470            },
471            queue_type: QueueType::OutputStateV2 as u64,
472            associated_merkle_tree: params.associated_merkle_tree,
473        };
474        let batch_metadata =
475            QueueBatches::new_output_queue(params.batch_size, params.zkp_batch_size).unwrap();
476        BatchedQueueMetadata {
477            metadata,
478            batch_metadata,
479            tree_capacity: 2u64.pow(params.height),
480            hashed_merkle_tree_pubkey: hash_to_bn254_field_size_be(
481                &params.associated_merkle_tree.to_bytes(),
482            ),
483            hashed_queue_pubkey: hash_to_bn254_field_size_be(&params.queue_pubkey.to_bytes()),
484        }
485    }
486}