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 pub index: u64,
24 pub program_owner: Option<Pubkey>,
26 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 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
66pub 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 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 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 {
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 0,
163 0,
164 output_queue_pubkey,
165 2u64.pow(params.height),
166 )?;
167 }
168 let metadata = MerkleTreeMetadata {
169 next_merkle_tree: Pubkey::default(),
170 access_metadata: AccessMetadata::new(owner, params.program_owner, params.forester),
171 rollover_metadata: RolloverMetadata::new(
172 params.index,
173 0,
176 params.rollover_threshold,
177 params.network_fee.unwrap_or_default(),
178 params.close_threshold,
179 None,
180 ),
181 associated_queue: output_queue_pubkey,
182 };
183
184 BatchedMerkleTreeAccount::init(
189 mt_account_data,
190 &mt_pubkey,
191 metadata,
192 params.root_history_capacity,
193 params.input_queue_batch_size,
194 params.input_queue_zkp_batch_size,
195 height,
196 params.bloom_filter_num_iters,
197 params.bloom_filter_capacity,
198 TreeType::StateV2,
199 )
200}
201
202pub fn validate_batched_tree_params(params: InitStateTreeAccountsInstructionData) {
204 assert!(params.input_queue_batch_size > 0);
205 assert!(params.output_queue_batch_size > 0);
206 assert_eq!(
207 params.input_queue_batch_size % params.input_queue_zkp_batch_size,
208 0,
209 "Input queue batch size must divisible by input_queue_zkp_batch_size."
210 );
211 assert_eq!(
212 params.output_queue_batch_size % params.output_queue_zkp_batch_size,
213 0,
214 "Output queue batch size must divisible by output_queue_zkp_batch_size."
215 );
216 assert!(
217 match_circuit_size(params.input_queue_zkp_batch_size),
218 "Zkp batch size not supported. Supported 10, 500"
219 );
220 assert!(
221 match_circuit_size(params.output_queue_zkp_batch_size),
222 "Zkp batch size not supported. Supported 10, 500"
223 );
224
225 assert!(params.bloom_filter_num_iters > 0);
226 assert!(params.bloom_filter_capacity >= params.input_queue_batch_size * 8);
227 assert_eq!(
228 params.bloom_filter_capacity % 8,
229 0,
230 "Bloom filter capacity must be divisible by 8."
231 );
232 assert!(params.bloom_filter_capacity > 0);
233 assert!(params.root_history_capacity > 0);
234 assert!(params.input_queue_batch_size > 0);
235
236 let required_capacity = (params.output_queue_batch_size / params.output_queue_zkp_batch_size)
238 + (params.input_queue_batch_size / params.input_queue_zkp_batch_size);
239 assert!(
240 params.root_history_capacity >= required_capacity as u32,
241 "root_history_capacity ({}) must be >= {} (output_queue_batch_size / output_queue_zkp_batch_size + input_queue_batch_size / input_queue_zkp_batch_size)",
242 params.root_history_capacity,
243 required_capacity
244 );
245
246 assert_eq!(params.close_threshold, None);
247 assert_eq!(params.height, DEFAULT_BATCH_STATE_TREE_HEIGHT);
248}
249
250pub fn match_circuit_size(size: u64) -> bool {
252 matches!(size, 10 | 500)
253}
254
255#[test]
256fn test_validate_root_history_capacity_state_tree() {
257 let params = InitStateTreeAccountsInstructionData::default();
259 validate_batched_tree_params(params); }
261
262#[test]
263#[should_panic(expected = "root_history_capacity")]
264fn test_validate_root_history_capacity_insufficient_state_tree() {
265 let params = InitStateTreeAccountsInstructionData {
266 root_history_capacity: 1, input_queue_batch_size: 1000,
268 output_queue_batch_size: 1000,
269 input_queue_zkp_batch_size: 10,
270 output_queue_zkp_batch_size: 10,
271 ..Default::default()
273 };
274 validate_batched_tree_params(params); }
276#[cfg(feature = "test-only")]
277pub mod test_utils {
278 use light_compressed_account::hash_to_bn254_field_size_be;
279
280 pub use super::InitStateTreeAccountsInstructionData;
281 use super::*;
282 use crate::{
283 constants::{
284 DEFAULT_BATCH_ROOT_HISTORY_LEN, STATE_BLOOM_FILTER_CAPACITY,
285 STATE_BLOOM_FILTER_NUM_HASHES, TEST_DEFAULT_BATCH_SIZE, TEST_DEFAULT_ZKP_BATCH_SIZE,
286 },
287 queue::{test_utils::assert_queue_inited, BatchedQueueMetadata},
288 queue_batch_metadata::QueueBatches,
289 };
290
291 impl InitStateTreeAccountsInstructionData {
292 pub fn test_default() -> Self {
293 Self {
294 index: 0,
295 program_owner: None,
296 forester: None,
297 additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
298 bloom_filter_num_iters: 3,
299 input_queue_batch_size: TEST_DEFAULT_BATCH_SIZE,
300 output_queue_batch_size: TEST_DEFAULT_BATCH_SIZE,
301 input_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
302 output_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
303 height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
304 root_history_capacity: DEFAULT_BATCH_ROOT_HISTORY_LEN,
305 bloom_filter_capacity: 20_000 * 8,
306 network_fee: Some(5000),
307 rollover_threshold: Some(95),
308 close_threshold: None,
309 }
310 }
311
312 pub fn e2e_test_default() -> Self {
313 Self {
314 index: 0,
315 program_owner: None,
316 forester: None,
317 additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
318 bloom_filter_num_iters: 3,
319 input_queue_batch_size: 500,
320 output_queue_batch_size: 500,
321 input_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
322 output_queue_zkp_batch_size: TEST_DEFAULT_ZKP_BATCH_SIZE,
323 height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
324 root_history_capacity: DEFAULT_BATCH_ROOT_HISTORY_LEN,
325 bloom_filter_capacity: 20_000 * 8,
326 network_fee: Some(5000),
327 rollover_threshold: Some(95),
328 close_threshold: None,
329 }
330 }
331
332 pub fn testnet_default() -> Self {
333 Self {
334 index: 0,
335 program_owner: None,
336 forester: None,
337 additional_bytes: DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE,
338 bloom_filter_num_iters: STATE_BLOOM_FILTER_NUM_HASHES,
339 input_queue_batch_size: 15000,
340 output_queue_batch_size: 15000,
341 input_queue_zkp_batch_size: DEFAULT_ZKP_BATCH_SIZE,
342 output_queue_zkp_batch_size: DEFAULT_ZKP_BATCH_SIZE,
343 height: DEFAULT_BATCH_STATE_TREE_HEIGHT,
344 root_history_capacity: DEFAULT_BATCH_ROOT_HISTORY_LEN,
345 bloom_filter_capacity: STATE_BLOOM_FILTER_CAPACITY,
346 network_fee: Some(5000),
347 rollover_threshold: Some(95),
348 close_threshold: None,
349 }
350 }
351 }
352
353 pub fn get_state_merkle_tree_account_size_from_params(
354 params: InitStateTreeAccountsInstructionData,
355 ) -> usize {
356 crate::merkle_tree::get_merkle_tree_account_size(
357 params.input_queue_batch_size,
358 params.bloom_filter_capacity,
359 params.input_queue_zkp_batch_size,
360 params.root_history_capacity,
361 params.height,
362 )
363 }
364
365 pub fn assert_state_mt_zero_copy_initialized(
366 account_data: &mut [u8],
367 ref_account: crate::merkle_tree_metadata::BatchedMerkleTreeMetadata,
368 pubkey: &Pubkey,
369 ) {
370 let account = BatchedMerkleTreeAccount::state_from_bytes(account_data, pubkey)
371 .expect("from_bytes_unchecked_mut failed");
372 _assert_mt_zero_copy_initialized::<{ light_compressed_account::STATE_MERKLE_TREE_TYPE_V2 }>(
373 account,
374 ref_account,
375 TreeType::StateV2 as u64,
376 );
377 }
378
379 pub fn assert_address_mt_zero_copy_initialized(
380 account_data: &mut [u8],
381 ref_account: crate::merkle_tree_metadata::BatchedMerkleTreeMetadata,
382 pubkey: &Pubkey,
383 ) {
384 use crate::merkle_tree::BatchedMerkleTreeAccount;
385
386 let account = BatchedMerkleTreeAccount::address_from_bytes(account_data, pubkey)
387 .expect("from_bytes_unchecked_mut failed");
388 _assert_mt_zero_copy_initialized::<{ light_compressed_account::STATE_MERKLE_TREE_TYPE_V2 }>(
389 account,
390 ref_account,
391 TreeType::AddressV1 as u64,
392 );
393 }
394
395 fn _assert_mt_zero_copy_initialized<const TREE_TYPE: u64>(
396 account: BatchedMerkleTreeAccount,
397 ref_account: crate::merkle_tree_metadata::BatchedMerkleTreeMetadata,
398 tree_type: u64,
399 ) {
400 use light_compressed_account::QueueType;
401 use light_hasher::Hasher;
402
403 let queue = account.queue_batches;
404 let ref_queue = ref_account.queue_batches;
405 assert_eq!(*account, ref_account, "metadata mismatch");
406
407 assert_eq!(
408 account.root_history.capacity(),
409 ref_account.root_history_capacity as usize,
410 "root_history_capacity mismatch"
411 );
412 if tree_type == TreeType::StateV2 as u64 {
413 assert_eq!(
414 *account.root_history.get(0).unwrap(),
415 light_hasher::Poseidon::zero_bytes()[ref_account.height as usize],
416 "root_history not initialized"
417 );
418 }
419 if tree_type == TreeType::AddressV2 as u64 {
420 assert_eq!(
421 *account.root_history.get(0).unwrap(),
422 crate::constants::ADDRESS_TREE_INIT_ROOT_40,
423 "root_history not initialized"
424 );
425 }
426 assert_eq!(
427 account.hash_chain_stores[0].capacity(),
428 ref_account.queue_batches.get_num_zkp_batches() as usize,
429 "hash_chain_store mismatch"
430 );
431
432 let queue_type = if tree_type == TreeType::StateV2 as u64 {
433 QueueType::InputStateV2 as u64
434 } else {
435 QueueType::AddressV2 as u64
436 };
437 assert_queue_inited(queue, ref_queue, queue_type, &mut []);
438 }
439
440 #[derive(Debug, Clone, Copy)]
441 #[repr(C)]
442 pub struct CreateOutputQueueParams {
443 pub owner: Pubkey,
444 pub program_owner: Option<Pubkey>,
445 pub forester: Option<Pubkey>,
446 pub rollover_threshold: Option<u64>,
447 pub index: u64,
448 pub batch_size: u64,
449 pub zkp_batch_size: u64,
450 pub additional_bytes: u64,
451 pub rent: u64,
452 pub associated_merkle_tree: Pubkey,
453 pub queue_pubkey: Pubkey,
454 pub height: u32,
455 pub network_fee: u64,
456 }
457
458 impl CreateOutputQueueParams {
459 pub fn from(
460 params: InitStateTreeAccountsInstructionData,
461 owner: Pubkey,
462 rent: u64,
463 associated_merkle_tree: Pubkey,
464 queue_pubkey: Pubkey,
465 ) -> Self {
466 Self {
467 owner,
468 program_owner: params.program_owner,
469 forester: params.forester,
470 rollover_threshold: params.rollover_threshold,
471 index: params.index,
472 batch_size: params.output_queue_batch_size,
473 zkp_batch_size: params.output_queue_zkp_batch_size,
474 additional_bytes: params.additional_bytes,
475 rent,
476 associated_merkle_tree,
477 height: params.height,
478 network_fee: params.network_fee.unwrap_or_default(),
479 queue_pubkey,
480 }
481 }
482 }
483
484 pub fn create_output_queue_account(params: CreateOutputQueueParams) -> BatchedQueueMetadata {
485 let rollover_fee: u64 = match params.rollover_threshold {
486 Some(rollover_threshold) => {
487 compute_rollover_fee(rollover_threshold, params.height, params.rent).unwrap()
488 }
489 None => 0,
490 };
491 let metadata = QueueMetadata {
492 next_queue: Pubkey::default(),
493 access_metadata: AccessMetadata {
494 owner: params.owner,
495 program_owner: params.program_owner.unwrap_or_default(),
496 forester: params.forester.unwrap_or_default(),
497 },
498 rollover_metadata: RolloverMetadata {
499 close_threshold: u64::MAX,
500 index: params.index,
501 rolledover_slot: u64::MAX,
502 rollover_threshold: params.rollover_threshold.unwrap_or(u64::MAX),
503 rollover_fee,
504 network_fee: params.network_fee,
505 additional_bytes: params.additional_bytes,
506 },
507 queue_type: QueueType::OutputStateV2 as u64,
508 associated_merkle_tree: params.associated_merkle_tree,
509 };
510 let batch_metadata =
511 QueueBatches::new_output_queue(params.batch_size, params.zkp_batch_size).unwrap();
512 BatchedQueueMetadata {
513 metadata,
514 batch_metadata,
515 tree_capacity: 2u64.pow(params.height),
516 hashed_merkle_tree_pubkey: hash_to_bn254_field_size_be(
517 ¶ms.associated_merkle_tree.to_bytes(),
518 ),
519 hashed_queue_pubkey: hash_to_bn254_field_size_be(¶ms.queue_pubkey.to_bytes()),
520 }
521 }
522}