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 )?;
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 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 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 ¶ms.associated_merkle_tree.to_bytes(),
482 ),
483 hashed_queue_pubkey: hash_to_bn254_field_size_be(¶ms.queue_pubkey.to_bytes()),
484 }
485 }
486}