1+ use light_array_map:: ArrayMap ;
12use light_compressed_account:: {
23 hash_to_bn254_field_size_be,
34 instruction_data:: {
@@ -10,6 +11,7 @@ use light_hasher::{Hasher, Poseidon};
1011use light_program_profiler:: profile;
1112use pinocchio:: { account_info:: AccountInfo , msg, program_error:: ProgramError } ;
1213
14+ use super :: tree_leaf_tracker_ext:: TreeLeafTrackerTupleExt ;
1315use crate :: {
1416 accounts:: remaining_account_checks:: AcpAccount ,
1517 context:: { SystemContext , WrappedInstructionData } ,
@@ -42,18 +44,15 @@ pub fn create_outputs_cpi_data<'a, 'info, T: InstructionData<'a>>(
4244 if inputs. output_len ( ) == 0 {
4345 return Ok ( [ 0u8 ; 32 ] ) ;
4446 }
45- let mut current_index: i16 = -1 ;
46- let mut num_leaves_in_tree: u32 = 0 ;
47- let mut mt_next_index: u32 = 0 ;
4847 let mut hashed_merkle_tree = [ 0u8 ; 32 ] ;
4948 cpi_ix_data. start_output_appends = context. account_indices . len ( ) as u8 ;
50- let mut index_merkle_tree_account_account = cpi_ix_data. start_output_appends ;
49+ // TODO: check correct index use and deduplicate if possible.
50+ let mut current_index: i16 = -1 ;
51+ let mut next_account_index = cpi_ix_data. start_output_appends ;
5152 let mut index_merkle_tree_account = 0 ;
52- let number_of_merkle_trees =
53- inputs. output_accounts ( ) . last ( ) . unwrap ( ) . merkle_tree_index ( ) as usize + 1 ;
5453
55- let mut merkle_tree_pubkeys =
56- Vec :: < light_compressed_account :: pubkey :: Pubkey > :: with_capacity ( number_of_merkle_trees ) ;
54+ // Track (tree_pubkey, (next_leaf_index, account_index)) for each unique tree
55+ let mut tree_leaf_tracker = ArrayMap :: < [ u8 ; 32 ] , ( u64 , u8 ) , 30 > :: new ( ) ;
5756 let mut hash_chain = [ 0u8 ; 32 ] ;
5857 let mut rollover_fee = 0 ;
5958 let mut is_batched = true ;
@@ -62,50 +61,95 @@ pub fn create_outputs_cpi_data<'a, 'info, T: InstructionData<'a>>(
6261 // if mt index == current index Merkle tree account info has already been added.
6362 // if mt index != current index, Merkle tree account info is new, add it.
6463 #[ allow( clippy:: comparison_chain) ]
65- if account. merkle_tree_index ( ) as i16 == current_index {
66- // Do nothing, but it is the most common case.
67- } else if account. merkle_tree_index ( ) as i16 > current_index {
64+ let ( leaf_index, account_index) = if account. merkle_tree_index ( ) as i16 == current_index {
65+ // Same tree as previous iteration - just increment leaf index
66+ tree_leaf_tracker. increment_current_tuple ( ) ?
67+ } else {
6868 current_index = account. merkle_tree_index ( ) . into ( ) ;
69-
70- let pubkey = match & accounts
69+ // Get tree/queue pubkey and metadata
70+ match & accounts
7171 . get ( current_index as usize )
7272 . ok_or ( SystemProgramError :: OutputMerkleTreeIndexOutOfBounds ) ?
7373 {
7474 AcpAccount :: OutputQueue ( output_queue) => {
75- context. set_network_fee (
76- output_queue. metadata . rollover_metadata . network_fee ,
77- current_index as u8 ,
78- ) ;
75+ let initial_leaf_index = output_queue. batch_metadata . next_index ;
76+
77+ // Get or insert tree entry - returns ((leaf_idx, account_idx), is_new)
78+ let ( ( leaf_idx, account_idx) , is_new) = tree_leaf_tracker. get_or_insert_tuple (
79+ output_queue. pubkey ( ) . array_ref ( ) ,
80+ ( initial_leaf_index, next_account_index) ,
81+ SystemProgramError :: TooManyOutputV2Queues ,
82+ ) ?;
7983
80- hashed_merkle_tree = output_queue. hashed_merkle_tree_pubkey ;
81- rollover_fee = output_queue. metadata . rollover_metadata . rollover_fee ;
82- mt_next_index = output_queue. batch_metadata . next_index as u32 ;
83- cpi_ix_data. output_sequence_numbers [ index_merkle_tree_account as usize ] =
84- MerkleTreeSequenceNumber {
85- tree_pubkey : output_queue. metadata . associated_merkle_tree ,
86- queue_pubkey : * output_queue. pubkey ( ) ,
87- tree_type : ( TreeType :: StateV2 as u64 ) . into ( ) ,
88- seq : output_queue. batch_metadata . next_index . into ( ) ,
89- } ;
90- is_batched = true ;
91- * output_queue. pubkey ( )
84+ // Only set up metadata if this is a new tree (first time seeing this pubkey)
85+ if is_new {
86+ // TODO: depulicate logic
87+ context. set_network_fee (
88+ output_queue. metadata . rollover_metadata . network_fee ,
89+ current_index as u8 ,
90+ ) ;
91+ hashed_merkle_tree = output_queue. hashed_merkle_tree_pubkey ;
92+ rollover_fee = output_queue. metadata . rollover_metadata . rollover_fee ;
93+ is_batched = true ;
94+
95+ cpi_ix_data. output_sequence_numbers [ index_merkle_tree_account as usize ] =
96+ MerkleTreeSequenceNumber {
97+ tree_pubkey : output_queue. metadata . associated_merkle_tree ,
98+ queue_pubkey : * output_queue. pubkey ( ) ,
99+ tree_type : ( TreeType :: StateV2 as u64 ) . into ( ) ,
100+ seq : initial_leaf_index. into ( ) ,
101+ } ;
102+
103+ context. get_index_or_insert (
104+ account. merkle_tree_index ( ) ,
105+ remaining_accounts,
106+ "Output queue for V2 state trees (Merkle tree for V1 state trees)" ,
107+ ) ?;
108+
109+ index_merkle_tree_account += 1 ;
110+ next_account_index += 1 ;
111+ }
112+
113+ ( leaf_idx, account_idx)
92114 }
93115 AcpAccount :: StateTree ( ( pubkey, tree) ) => {
94- cpi_ix_data. output_sequence_numbers [ index_merkle_tree_account as usize ] =
95- MerkleTreeSequenceNumber {
96- tree_pubkey : * pubkey,
97- queue_pubkey : * pubkey,
98- tree_type : ( TreeType :: StateV1 as u64 ) . into ( ) ,
99- seq : ( tree. sequence_number ( ) as u64 + 1 ) . into ( ) ,
100- } ;
101- let merkle_context = context
102- . get_legacy_merkle_context ( current_index as u8 )
103- . unwrap ( ) ;
104- hashed_merkle_tree = merkle_context. hashed_pubkey ;
105- rollover_fee = merkle_context. rollover_fee ;
106- mt_next_index = tree. next_index ( ) as u32 ;
107- is_batched = false ;
108- * pubkey
116+ let initial_leaf_index = tree. next_index ( ) as u64 ;
117+
118+ // Get or insert tree entry - returns ((leaf_idx, account_idx), is_new)
119+ let ( ( leaf_idx, account_idx) , is_new) = tree_leaf_tracker. get_or_insert_tuple (
120+ pubkey. array_ref ( ) ,
121+ ( initial_leaf_index, next_account_index) ,
122+ SystemProgramError :: TooManyOutputV1Trees ,
123+ ) ?;
124+
125+ // Only set up metadata if this is a new tree (first time seeing this pubkey)
126+ if is_new {
127+ cpi_ix_data. output_sequence_numbers [ index_merkle_tree_account as usize ] =
128+ MerkleTreeSequenceNumber {
129+ tree_pubkey : * pubkey,
130+ queue_pubkey : * pubkey,
131+ tree_type : ( TreeType :: StateV1 as u64 ) . into ( ) ,
132+ seq : ( tree. sequence_number ( ) as u64 + 1 ) . into ( ) ,
133+ } ;
134+
135+ let merkle_context = context
136+ . get_legacy_merkle_context ( current_index as u8 )
137+ . unwrap ( ) ;
138+ hashed_merkle_tree = merkle_context. hashed_pubkey ;
139+ rollover_fee = merkle_context. rollover_fee ;
140+ is_batched = false ;
141+
142+ context. get_index_or_insert (
143+ account. merkle_tree_index ( ) ,
144+ remaining_accounts,
145+ "Output queue for V2 state trees (Merkle tree for V1 state trees)" ,
146+ ) ?;
147+
148+ index_merkle_tree_account += 1 ;
149+ next_account_index += 1 ;
150+ }
151+
152+ ( leaf_idx, account_idx)
109153 }
110154 AcpAccount :: Unknown ( ) => {
111155 msg ! (
@@ -144,30 +188,8 @@ pub fn create_outputs_cpi_data<'a, 'info, T: InstructionData<'a>>(
144188 SystemProgramError :: StateMerkleTreeAccountDiscriminatorMismatch . into ( ) ,
145189 ) ;
146190 }
147- } ;
148- // check Merkle tree uniqueness
149- if merkle_tree_pubkeys. contains ( & pubkey) {
150- return Err ( SystemProgramError :: OutputMerkleTreeNotUnique . into ( ) ) ;
151- } else {
152- merkle_tree_pubkeys. push ( pubkey) ;
153191 }
154-
155- context. get_index_or_insert (
156- account. merkle_tree_index ( ) ,
157- remaining_accounts,
158- "Output queue for V2 state trees (Merkle tree for V1 state trees)" ,
159- ) ?;
160- num_leaves_in_tree = 0 ;
161- index_merkle_tree_account += 1 ;
162- index_merkle_tree_account_account += 1 ;
163- } else {
164- // Check 2.
165- // Output Merkle tree indices must be in order since we use the
166- // number of leaves in a Merkle tree to determine the correct leaf
167- // index. Since the leaf index is part of the hash this is security
168- // critical.
169- return Err ( SystemProgramError :: OutputMerkleTreeIndicesNotInOrder . into ( ) ) ;
170- }
192+ } ;
171193
172194 // Check 3.
173195 if let Some ( address) = account. address ( ) {
@@ -183,9 +205,11 @@ pub fn create_outputs_cpi_data<'a, 'info, T: InstructionData<'a>>(
183205 return Err ( SystemProgramError :: InvalidAddress . into ( ) ) ;
184206 }
185207 }
186- cpi_ix_data. output_leaf_indices [ j] = ( mt_next_index + num_leaves_in_tree) . into ( ) ;
187208
188- num_leaves_in_tree += 1 ;
209+ // Use the tracked leaf index from our ArrayVec
210+ cpi_ix_data. output_leaf_indices [ j] = u32:: try_from ( leaf_index)
211+ . map_err ( |_| SystemProgramError :: PackedAccountIndexOutOfBounds ) ?
212+ . into ( ) ;
189213 if account. has_data ( ) && context. invoking_program_id . is_none ( ) {
190214 msg ! ( "Invoking program is not provided." ) ;
191215 msg ! ( "Only program owned compressed accounts can have data." ) ;
@@ -214,7 +238,7 @@ pub fn create_outputs_cpi_data<'a, 'info, T: InstructionData<'a>>(
214238 is_batched,
215239 )
216240 . map_err ( ProgramError :: from) ?;
217- cpi_ix_data. leaves [ j] . account_index = index_merkle_tree_account_account - 1 ;
241+ cpi_ix_data. leaves [ j] . account_index = account_index ;
218242
219243 if !cpi_ix_data. nullifiers . is_empty ( ) {
220244 if j == 0 {
0 commit comments