account_compression/processor/
insert_nullifiers.rs

1use anchor_lang::prelude::*;
2use light_batched_merkle_tree::queue::BatchedQueueAccount;
3use light_compressed_account::instruction_data::insert_into_queues::InsertNullifierInput;
4use num_bigint::BigUint;
5
6use crate::{
7    context::AcpAccount, errors::AccountCompressionErrorCode,
8    insert_into_queues::get_queue_and_tree_accounts, queue_from_bytes_zero_copy_mut, QueueAccount,
9};
10
11#[inline(always)]
12pub fn insert_nullifiers(
13    num_queues: u8,
14    tx_hash: [u8; 32],
15    nullifiers: &[InsertNullifierInput],
16    accounts: &mut [AcpAccount<'_, '_>],
17    current_slot: &u64,
18) -> Result<()> {
19    if nullifiers.is_empty() {
20        return Ok(());
21    }
22
23    // 1. Gather unique (tree_index, queue_index) pairs in the order they appear,
24    //    capped at `num_queues`.
25    let mut visited = Vec::with_capacity(num_queues as usize);
26    // Always push the first one
27    visited.push((nullifiers[0].tree_index, nullifiers[0].queue_index));
28
29    for nf in nullifiers.iter().skip(1) {
30        // Stop once we have reached num_queues
31        if visited.len() == num_queues as usize {
32            break;
33        }
34        // Only insert if this queue_index hasn't been added yet
35        if visited.iter().all(|&(_, q)| q != nf.queue_index) {
36            visited.push((nf.tree_index, nf.queue_index));
37        }
38    }
39
40    let mut inserted_nullifiers = 0;
41
42    // 2. For each unique queue_index, get the corresponding accounts and process nullifiers.
43    for &(tree_index, queue_index) in &visited {
44        // Lookup the queue and tree accounts
45        let (queue_account, merkle_tree_account) =
46            get_queue_and_tree_accounts(accounts, queue_index as usize, tree_index as usize)?;
47
48        // Dispatch to v1 / v2 / ... based on the account type
49        match queue_account {
50            AcpAccount::OutputQueue(queue) => {
51                inserted_nullifiers += batched_nullifiers(
52                    merkle_tree_account,
53                    queue,
54                    &tx_hash,
55                    nullifiers,
56                    queue_index,
57                    tree_index,
58                    current_slot,
59                )?;
60                anchor_lang::Result::Ok(())
61            }
62            AcpAccount::V1Queue(queue_account_info) => {
63                inserted_nullifiers += process_nullifiers_v1(
64                    merkle_tree_account,
65                    queue_account_info,
66                    nullifiers,
67                    queue_index,
68                    tree_index,
69                )?;
70                Ok(())
71            }
72            AcpAccount::BatchedStateTree(_) => {
73                msg!("BatchedStateTree, expected output queue.");
74                Err(AccountCompressionErrorCode::InvalidAccount.into())
75            }
76            AcpAccount::StateTree(_) => {
77                msg!("StateTree, expected v1 nullifier queue.");
78                Err(AccountCompressionErrorCode::InvalidAccount.into())
79            }
80            AcpAccount::BatchedAddressTree(_) => {
81                msg!("BatchedAddressTree, expected v1 nullifier or output queue.");
82                Err(AccountCompressionErrorCode::InvalidAccount.into())
83            }
84            _ => Err(AccountCompressionErrorCode::InvalidAccount.into()),
85        }?;
86    }
87
88    // 3. Verify we inserted a nullifier for all items
89    if inserted_nullifiers != nullifiers.len() {
90        msg!("inserted_nullifiers {:?}", inserted_nullifiers);
91        msg!("nullifiers.len() {:?}", nullifiers.len());
92        return err!(AccountCompressionErrorCode::NotAllLeavesProcessed);
93    }
94
95    Ok(())
96}
97
98/// Steps:
99/// 1. filter for nullifiers with the same queue and tree indices
100/// 2. unpack tree account, fail if account is not a tree account
101/// 3. check queue and tree are associated
102/// 4. check for every value whether it exists in the queue and zero it out.
103///    If checked fail if the value is not in the queue.
104/// 5. Insert the nullifiers into the current input queue batch.
105#[inline(always)]
106fn batched_nullifiers<'info>(
107    merkle_tree: &mut AcpAccount<'_, 'info>,
108    output_queue: &mut BatchedQueueAccount<'info>,
109    tx_hash: &[u8; 32],
110    nullifiers: &[InsertNullifierInput],
111    current_queue_index: u8,
112    current_tree_index: u8,
113    current_slot: &u64,
114) -> Result<usize> {
115    // 1. filter for nullifiers with the same queue and tree indices
116    let nullifiers = nullifiers
117        .iter()
118        .filter(|x| x.queue_index == current_queue_index && x.tree_index == current_tree_index);
119    let merkle_tree = if let AcpAccount::BatchedStateTree(tree) = merkle_tree {
120        tree
121    } else {
122        return err!(AccountCompressionErrorCode::StateMerkleTreeAccountDiscriminatorMismatch);
123    };
124    // 3. Check queue and Merkle tree are associated.
125    output_queue
126        .check_is_associated(merkle_tree.pubkey())
127        .map_err(ProgramError::from)?;
128
129    let mut num_elements = 0;
130
131    for nullifier in nullifiers {
132        num_elements += 1;
133        // 4. check for every account whether the value is still in the queue and zero it out.
134        //      If checked fail if the value is not in the queue.
135        let leaf_index = nullifier.leaf_index.into();
136        output_queue
137            .prove_inclusion_by_index_and_zero_out_leaf(
138                leaf_index,
139                &nullifier.account_hash,
140                nullifier.prove_by_index(),
141            )
142            .map_err(ProgramError::from)?;
143
144        // 5. Insert the nullifiers into the current input queue batch.
145        merkle_tree
146            .insert_nullifier_into_queue(&nullifier.account_hash, leaf_index, tx_hash, current_slot)
147            .map_err(ProgramError::from)?;
148    }
149    Ok(num_elements)
150}
151
152/// Steps:
153/// 1. filter for nullifiers with the same queue and tree indices
154/// 2. unpack tree account, fail if account is not a tree account
155/// 3. check queue and tree are associated
156/// 4. Insert the nullifiers into the queues hash set.
157fn process_nullifiers_v1<'info>(
158    merkle_tree: &mut AcpAccount<'_, 'info>,
159    nullifier_queue: &mut AccountInfo<'info>,
160    nullifiers: &[InsertNullifierInput],
161    current_queue_index: u8,
162    current_tree_index: u8,
163) -> Result<usize> {
164    let nullifiers = nullifiers
165        .iter()
166        .filter(|x| x.queue_index == current_queue_index && x.tree_index == current_tree_index);
167    let (merkle_pubkey, merkle_tree) = if let AcpAccount::StateTree(tree) = merkle_tree {
168        tree
169    } else {
170        return err!(AccountCompressionErrorCode::StateMerkleTreeAccountDiscriminatorMismatch);
171    };
172
173    {
174        let queue_data = nullifier_queue.try_borrow_data()?;
175        // Discriminator is already checked in try_from_account_infos.
176        let queue = bytemuck::from_bytes::<QueueAccount>(&queue_data[8..QueueAccount::LEN]);
177        // 3. Check queue and Merkle tree are associated.
178        if queue.metadata.associated_merkle_tree != *merkle_pubkey {
179            msg!(
180                "Queue account {:?} is not associated with Merkle tree  {:?}",
181                nullifier_queue.key(),
182                *merkle_pubkey
183            );
184            return err!(AccountCompressionErrorCode::MerkleTreeAndQueueNotAssociated);
185        }
186    }
187    let mut num_elements = 0;
188    // 4. Insert the nullifiers into the queues hash set.
189
190    let sequence_number = merkle_tree.sequence_number();
191    let mut queue = nullifier_queue.try_borrow_mut_data()?;
192    let mut queue = unsafe { queue_from_bytes_zero_copy_mut(&mut queue).unwrap() };
193    #[cfg(feature = "bench-sbf")]
194    light_heap::bench_sbf_start!("acp_insert_nf_into_queue");
195    for nullifier in nullifiers {
196        if nullifier.prove_by_index() {
197            return Err(AccountCompressionErrorCode::V1AccountMarkedAsProofByIndex.into());
198        }
199        num_elements += 1;
200        let element = BigUint::from_bytes_be(nullifier.account_hash.as_slice());
201        queue
202            .insert(&element, sequence_number)
203            .map_err(ProgramError::from)?;
204    }
205    #[cfg(feature = "bench-sbf")]
206    light_heap::bench_sbf_end!("acp_insert_nf_into_queue");
207    Ok(num_elements)
208}