From 4bc5eaef3c5cc3a8ad9a31bb00853e087778eb36 Mon Sep 17 00:00:00 2001 From: ananas Date: Tue, 3 Mar 2026 16:50:23 +0000 Subject: [PATCH 1/2] chore: limit v1 state tree, v2 state&address tree creations to protocol authority Entire-Checkpoint: beb8e7abdeef --- .../tests/batched_merkle_tree_test.rs | 1 + program-tests/registry-test/tests/tests.rs | 74 ++++++++++++++++++- program-tests/system-test/tests/test.rs | 7 +- program-tests/utils/src/e2e_test_env.rs | 1 + .../src/account_compression_cpi/sdk.rs | 12 +-- programs/registry/src/lib.rs | 9 +++ .../src/accounts/address_tree_v2.rs | 9 ++- .../program-test/src/accounts/initialize.rs | 4 + .../program-test/src/accounts/state_tree.rs | 19 +++-- .../src/accounts/state_tree_v2.rs | 21 ++++-- .../program-test/src/indexer/test_indexer.rs | 19 ++++- xtask/src/create_batch_address_tree.rs | 2 +- xtask/src/create_batch_state_tree.rs | 1 + xtask/src/create_state_tree.rs | 1 + xtask/src/new_deployment.rs | 2 + 15 files changed, 152 insertions(+), 30 deletions(-) diff --git a/program-tests/account-compression-test/tests/batched_merkle_tree_test.rs b/program-tests/account-compression-test/tests/batched_merkle_tree_test.rs index 018e1192f5..23207121d6 100644 --- a/program-tests/account-compression-test/tests/batched_merkle_tree_test.rs +++ b/program-tests/account-compression-test/tests/batched_merkle_tree_test.rs @@ -379,6 +379,7 @@ async fn test_batch_state_merkle_tree() { let new_keypair_queue = Keypair::new(); let payer = context.get_payer().insecure_clone(); create_batched_state_merkle_tree( + &payer, &payer, false, &mut context, diff --git a/program-tests/registry-test/tests/tests.rs b/program-tests/registry-test/tests/tests.rs index 569959f476..d93776fd97 100644 --- a/program-tests/registry-test/tests/tests.rs +++ b/program-tests/registry-test/tests/tests.rs @@ -31,6 +31,7 @@ use light_program_test::{ deregister_program_with_registry_program, register_program_with_registry_program, }, state_tree::create_state_merkle_tree_and_queue_account, + state_tree_v2::create_batched_state_merkle_tree, test_accounts::{TestAccounts, NOOP_PROGRAM_ID}, test_keypairs::{GROUP_PDA_SEED_TEST_KEYPAIR, OLD_REGISTRY_ID_TEST_KEYPAIR}, }, @@ -387,6 +388,7 @@ async fn test_initialize_protocol_config() { let nullifier_queue_keypair = Keypair::new(); let cpi_context_keypair = Keypair::new(); create_state_merkle_tree_and_queue_account( + &payer, &payer, true, &mut rpc, @@ -411,6 +413,7 @@ async fn test_initialize_protocol_config() { let nullifier_queue_keypair = Keypair::new(); let cpi_context_keypair = Keypair::new(); let result = create_state_merkle_tree_and_queue_account( + &payer, &payer, true, &mut rpc, @@ -436,6 +439,7 @@ async fn test_initialize_protocol_config() { let cpi_context_keypair = Keypair::new(); create_state_merkle_tree_and_queue_account( &payer, + &updated_keypair, true, &mut rpc, &merkle_tree_keypair, @@ -453,12 +457,38 @@ async fn test_initialize_protocol_config() { .await .unwrap(); } + // FAIL: initialize a Merkle tree with network_fee but wrong authority + { + let merkle_tree_keypair = Keypair::new(); + let nullifier_queue_keypair = Keypair::new(); + let cpi_context_keypair = Keypair::new(); + let result = create_state_merkle_tree_and_queue_account( + &payer, + &payer, + true, + &mut rpc, + &merkle_tree_keypair, + &nullifier_queue_keypair, + Some(&cpi_context_keypair), + None, + None, + 1, + &StateMerkleTreeConfig { + network_fee: Some(5000), + ..Default::default() + }, + &NullifierQueueConfig::default(), + ) + .await; + assert_rpc_error(result, 3, RegistryError::InvalidSigner.into()).unwrap(); + } // FAIL: initialize a Merkle tree with network_fee + forester (must be rejected) { let merkle_tree_keypair = Keypair::new(); let nullifier_queue_keypair = Keypair::new(); let cpi_context_keypair = Keypair::new(); let result = create_state_merkle_tree_and_queue_account( + &payer, &payer, true, &mut rpc, @@ -483,6 +513,7 @@ async fn test_initialize_protocol_config() { let nullifier_queue_keypair = Keypair::new(); let cpi_context_keypair = Keypair::new(); let result = create_state_merkle_tree_and_queue_account( + &payer, &payer, true, &mut rpc, @@ -1875,11 +1906,48 @@ async fn test_batch_address_tree() { network_fee: Some(1), ..Default::default() }; - let result = - create_batch_address_merkle_tree(&mut rpc, &payer, &new_merkle_tree, test_tree_params) - .await; + let result = create_batch_address_merkle_tree( + &mut rpc, + &payer, + &payer, + &new_merkle_tree, + test_tree_params, + ) + .await; assert_rpc_error(result, 1, RegistryError::InvalidNetworkFee.into()).unwrap(); } + // FAIL: create batched address tree with correct fee but wrong authority + { + let new_merkle_tree = Keypair::new(); + let test_tree_params = InitAddressTreeAccountsInstructionData::default(); + let result = create_batch_address_merkle_tree( + &mut rpc, + &payer, + &payer, + &new_merkle_tree, + test_tree_params, + ) + .await; + assert_rpc_error(result, 1, RegistryError::InvalidSigner.into()).unwrap(); + } + // FAIL: create batched state tree with correct fee but wrong authority + { + let merkle_tree_keypair = Keypair::new(); + let queue_keypair = Keypair::new(); + let cpi_context_keypair = Keypair::new(); + let result = create_batched_state_merkle_tree( + &payer, + &payer, + true, + &mut rpc, + &merkle_tree_keypair, + &queue_keypair, + &cpi_context_keypair, + InitStateTreeAccountsInstructionData::default(), + ) + .await; + assert_rpc_error(result, 3, RegistryError::InvalidSigner.into()).unwrap(); + } for i in 0..tree_params.input_queue_batch_size * 2 { println!("tx {}", i); diff --git a/program-tests/system-test/tests/test.rs b/program-tests/system-test/tests/test.rs index 5768a9a66f..45d7ee4137 100644 --- a/program-tests/system-test/tests/test.rs +++ b/program-tests/system-test/tests/test.rs @@ -20,7 +20,6 @@ use light_compressed_account::{ }, TreeType, }; -use light_merkle_tree_metadata::errors::MerkleTreeMetadataError; use light_program_test::{ accounts::test_accounts::TestAccounts, indexer::{TestIndexer, TestIndexerExtensions}, @@ -623,7 +622,9 @@ pub async fn failing_transaction_address( .unwrap(); } - // invalid address queue account + // invalid address queue account (replaced with nullifier queue) + // The system program hits MissingLegacyMerkleContext (error code 6066) + // when trying to look up the legacy context for the replaced queue account. { let inputs_struct = inputs_struct.clone(); let mut remaining_accounts = remaining_accounts.clone(); @@ -639,7 +640,7 @@ pub async fn failing_transaction_address( payer, inputs_struct, remaining_accounts.clone(), - MerkleTreeMetadataError::InvalidQueueType.into(), + 6066, // SystemProgramError::MissingLegacyMerkleContext ) .await .unwrap(); diff --git a/program-tests/utils/src/e2e_test_env.rs b/program-tests/utils/src/e2e_test_env.rs index b7ae6acc99..a5423e346d 100644 --- a/program-tests/utils/src/e2e_test_env.rs +++ b/program-tests/utils/src/e2e_test_env.rs @@ -1245,6 +1245,7 @@ where let forester = Pubkey::new_unique(); println!("queue config: {:?}", queue_config); create_state_merkle_tree_and_queue_account( + &self.payer, &self.payer, true, &mut self.rpc, diff --git a/programs/registry/src/account_compression_cpi/sdk.rs b/programs/registry/src/account_compression_cpi/sdk.rs index 1879ccb1f5..ce3d1d7717 100644 --- a/programs/registry/src/account_compression_cpi/sdk.rs +++ b/programs/registry/src/account_compression_cpi/sdk.rs @@ -289,7 +289,7 @@ pub fn create_initialize_address_merkle_tree_and_queue_instruction( } pub fn create_initialize_merkle_tree_instruction( - payer: Pubkey, + authority: Pubkey, merkle_tree_pubkey: Pubkey, nullifier_queue_pubkey: Pubkey, cpi_context_pubkey: Pubkey, @@ -309,7 +309,7 @@ pub fn create_initialize_merkle_tree_instruction( queue_config: nullifier_queue_config, }; let accounts = crate::accounts::InitializeMerkleTreeAndQueue { - authority: payer, + authority, registered_program_pda: register_program_pda, merkle_tree: merkle_tree_pubkey, queue: nullifier_queue_pubkey, @@ -327,7 +327,7 @@ pub fn create_initialize_merkle_tree_instruction( } pub fn create_initialize_batched_merkle_tree_instruction( - payer: Pubkey, + authority: Pubkey, merkle_tree_pubkey: Pubkey, queue_pubkey: Pubkey, cpi_context_pubkey: Pubkey, @@ -341,7 +341,7 @@ pub fn create_initialize_batched_merkle_tree_instruction( params: params.try_to_vec().unwrap(), }; let accounts = crate::accounts::InitializeBatchedStateMerkleTreeAndQueue { - authority: payer, + authority, registered_program_pda: register_program_pda, merkle_tree: merkle_tree_pubkey, queue: queue_pubkey, @@ -460,7 +460,7 @@ pub fn create_rollover_batch_state_tree_instruction( } pub fn create_initialize_batched_address_merkle_tree_instruction( - payer: Pubkey, + authority: Pubkey, merkle_tree_pubkey: Pubkey, params: InitAddressTreeAccountsInstructionData, ) -> Instruction { @@ -473,7 +473,7 @@ pub fn create_initialize_batched_address_merkle_tree_instruction( }; let protocol_config_pda = get_protocol_config_pda_address().0; let accounts = crate::accounts::InitializeBatchedAddressTree { - authority: payer, + authority, registered_program_pda: register_program_pda, merkle_tree: merkle_tree_pubkey, cpi_authority, diff --git a/programs/registry/src/lib.rs b/programs/registry/src/lib.rs index a4882c4ede..a21b58cd4b 100644 --- a/programs/registry/src/lib.rs +++ b/programs/registry/src/lib.rs @@ -345,6 +345,9 @@ pub mod light_registry { msg!("Forester pubkey must not be defined for trees serviced by light foresters."); return err!(RegistryError::ForesterDefined); } + if ctx.accounts.authority.key() != ctx.accounts.protocol_config_pda.authority { + return err!(RegistryError::InvalidSigner); + } } else if forester.is_none() { msg!("Forester pubkey required for trees without a network fee."); msg!("Trees without a network fee will not be serviced by light foresters."); @@ -518,6 +521,9 @@ pub mod light_registry { } else { return err!(RegistryError::InvalidNetworkFee); } + if ctx.accounts.authority.key() != ctx.accounts.protocol_config_pda.authority { + return err!(RegistryError::InvalidSigner); + } check_cpi_context( ctx.accounts.cpi_context_account.to_account_info(), &ctx.accounts.protocol_config_pda.config, @@ -606,6 +612,9 @@ pub mod light_registry { } else { return err!(RegistryError::InvalidNetworkFee); } + if ctx.accounts.authority.key() != ctx.accounts.protocol_config_pda.authority { + return err!(RegistryError::InvalidSigner); + } process_initialize_batched_address_merkle_tree(&ctx, bump, params.try_to_vec()?) } diff --git a/sdk-libs/program-test/src/accounts/address_tree_v2.rs b/sdk-libs/program-test/src/accounts/address_tree_v2.rs index fe9bccee9b..0a0d59e3c4 100644 --- a/sdk-libs/program-test/src/accounts/address_tree_v2.rs +++ b/sdk-libs/program-test/src/accounts/address_tree_v2.rs @@ -11,6 +11,7 @@ use crate::utils::create_account::create_account_instruction; pub async fn create_batch_address_merkle_tree( rpc: &mut R, payer: &Keypair, + authority: &Keypair, new_address_merkle_tree_keypair: &Keypair, address_tree_params: InitAddressTreeAccountsInstructionData, ) -> Result { @@ -34,14 +35,18 @@ pub async fn create_batch_address_merkle_tree( ); let instruction = create_initialize_batched_address_merkle_tree_instruction( - payer.pubkey(), + authority.pubkey(), new_address_merkle_tree_keypair.pubkey(), address_tree_params, ); + let mut signers: Vec<&Keypair> = vec![payer, new_address_merkle_tree_keypair]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } rpc.create_and_send_transaction( &[create_mt_account_ix, instruction], &payer.pubkey(), - &[payer, new_address_merkle_tree_keypair], + &signers, ) .await } diff --git a/sdk-libs/program-test/src/accounts/initialize.rs b/sdk-libs/program-test/src/accounts/initialize.rs index 37700d2393..f739b47be6 100644 --- a/sdk-libs/program-test/src/accounts/initialize.rs +++ b/sdk-libs/program-test/src/accounts/initialize.rs @@ -117,6 +117,7 @@ pub async fn initialize_accounts( } if !config.skip_v1_trees { create_state_merkle_tree_and_queue_account( + &keypairs.governance_authority, &keypairs.governance_authority, true, context, @@ -133,6 +134,7 @@ pub async fn initialize_accounts( if !skip_second_v1_tree { create_state_merkle_tree_and_queue_account( + &keypairs.governance_authority, &keypairs.governance_authority, true, context, @@ -168,6 +170,7 @@ pub async fn initialize_accounts( #[cfg(feature = "v2")] if let Some(v2_state_tree_config) = _v2_state_tree_config { create_batched_state_merkle_tree( + &keypairs.governance_authority, &keypairs.governance_authority, true, context, @@ -183,6 +186,7 @@ pub async fn initialize_accounts( create_batch_address_merkle_tree( context, &keypairs.governance_authority, + &keypairs.governance_authority, &keypairs.batch_address_merkle_tree, *params, ) diff --git a/sdk-libs/program-test/src/accounts/state_tree.rs b/sdk-libs/program-test/src/accounts/state_tree.rs index d43f7f768e..b39f7ec67f 100644 --- a/sdk-libs/program-test/src/accounts/state_tree.rs +++ b/sdk-libs/program-test/src/accounts/state_tree.rs @@ -105,6 +105,7 @@ pub fn create_insert_leaves_instruction( #[allow(clippy::too_many_arguments)] pub async fn create_state_merkle_tree_and_queue_account( payer: &Keypair, + authority: &Keypair, registry: bool, rpc: &mut R, merkle_tree_keypair: &Keypair, @@ -163,7 +164,7 @@ pub async fn create_state_merkle_tree_and_queue_account( ); let instruction = create_initialize_merkle_tree_instruction_registry( - payer.pubkey(), + authority.pubkey(), merkle_tree_keypair.pubkey(), nullifier_queue_keypair.pubkey(), cpi_context_keypair.pubkey(), @@ -172,6 +173,15 @@ pub async fn create_state_merkle_tree_and_queue_account( program_owner, forester, ); + let mut signers = vec![ + payer, + merkle_tree_keypair, + nullifier_queue_keypair, + cpi_context_keypair, + ]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } Transaction::new_signed_with_payer( &[ create_cpi_context_instruction, @@ -180,12 +190,7 @@ pub async fn create_state_merkle_tree_and_queue_account( instruction, ], Some(&payer.pubkey()), - &vec![ - payer, - merkle_tree_keypair, - nullifier_queue_keypair, - cpi_context_keypair, - ], + &signers, rpc.get_latest_blockhash().await?.0, ) } else { diff --git a/sdk-libs/program-test/src/accounts/state_tree_v2.rs b/sdk-libs/program-test/src/accounts/state_tree_v2.rs index b3c4bb6184..7095263d02 100644 --- a/sdk-libs/program-test/src/accounts/state_tree_v2.rs +++ b/sdk-libs/program-test/src/accounts/state_tree_v2.rs @@ -14,8 +14,10 @@ use solana_sdk::signature::{Keypair, Signature, Signer}; use crate::utils::create_account::create_account_instruction; +#[allow(clippy::too_many_arguments)] pub async fn create_batched_state_merkle_tree( payer: &Keypair, + authority: &Keypair, registry: bool, rpc: &mut R, merkle_tree_keypair: &Keypair, @@ -66,7 +68,7 @@ pub async fn create_batched_state_merkle_tree( ); let instruction = if registry { create_initialize_batched_merkle_tree_instruction( - payer.pubkey(), + authority.pubkey(), merkle_tree_keypair.pubkey(), queue_keypair.pubkey(), cpi_context_keypair.pubkey(), @@ -90,6 +92,16 @@ pub async fn create_batched_state_merkle_tree( } }; + let mut signers: Vec<&Keypair> = vec![ + payer, + merkle_tree_keypair, + queue_keypair, + cpi_context_keypair, + ]; + if registry && authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + rpc.create_and_send_transaction( &[ create_mt_account_ix, @@ -98,12 +110,7 @@ pub async fn create_batched_state_merkle_tree( instruction, ], &payer.pubkey(), - &[ - payer, - merkle_tree_keypair, - queue_keypair, - cpi_context_keypair, - ], + &signers, ) .await } diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index f01b9ec8e9..0b5b0583a3 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -100,6 +100,10 @@ pub struct TestIndexer { pub state_merkle_trees: Vec, pub address_merkle_trees: Vec, pub payer: Keypair, + /// Protocol authority keypair used as the `authority` signer when creating + /// new trees through the registry program (which enforces that the authority + /// matches `protocol_config_pda.authority`). + pub governance_authority: Keypair, pub group_pda: Pubkey, pub compressed_accounts: Vec, pub nullified_compressed_accounts: Vec, @@ -116,6 +120,7 @@ impl Clone for TestIndexer { state_merkle_trees: self.state_merkle_trees.clone(), address_merkle_trees: self.address_merkle_trees.clone(), payer: self.payer.insecure_clone(), + governance_authority: self.governance_authority.insecure_clone(), group_pda: self.group_pda, compressed_accounts: self.compressed_accounts.clone(), nullified_compressed_accounts: self.nullified_compressed_accounts.clone(), @@ -1286,6 +1291,7 @@ impl TestIndexer { state_merkle_tree_accounts, address_merkle_tree_accounts, payer.insecure_clone(), + env.protocol.governance_authority.insecure_clone(), env.protocol.group_pda, output_queue_batch_size, ) @@ -1296,6 +1302,7 @@ impl TestIndexer { state_merkle_tree_accounts: Vec, address_merkle_tree_accounts: Vec, payer: Keypair, + governance_authority: Keypair, group_pda: Pubkey, output_queue_batch_size: usize, ) -> Self { @@ -1347,6 +1354,7 @@ impl TestIndexer { state_merkle_trees, address_merkle_trees, payer, + governance_authority, compressed_accounts: vec![], nullified_compressed_accounts: vec![], events: vec![], @@ -1575,7 +1583,14 @@ impl TestIndexer { "Creating batched address merkle tree {:?}", merkle_tree_keypair.pubkey() ); - create_batch_address_merkle_tree(rpc, &self.payer, merkle_tree_keypair, params).await?; + create_batch_address_merkle_tree( + rpc, + &self.payer, + &self.governance_authority, + merkle_tree_keypair, + params, + ) + .await?; info!( "Batched address merkle tree created {:?}", merkle_tree_keypair.pubkey() @@ -1649,6 +1664,7 @@ impl TestIndexer { }; create_state_merkle_tree_and_queue_account( &self.payer, + &self.governance_authority, true, rpc, merkle_tree_keypair, @@ -1678,6 +1694,7 @@ impl TestIndexer { create_batched_state_merkle_tree( &self.payer, + &self.governance_authority, true, rpc, merkle_tree_keypair, diff --git a/xtask/src/create_batch_address_tree.rs b/xtask/src/create_batch_address_tree.rs index ffb39ef368..89bf53b1ab 100644 --- a/xtask/src/create_batch_address_tree.rs +++ b/xtask/src/create_batch_address_tree.rs @@ -93,7 +93,7 @@ pub async fn create_batch_address_tree(options: Options) -> anyhow::Result<()> { let balance = rpc.get_balance(&payer.pubkey()).await.unwrap(); println!("Payer balance: {:?}", balance); let tx_hash = - create_batch_address_merkle_tree(&mut rpc, &payer, merkle_tree_keypair, config) + create_batch_address_merkle_tree(&mut rpc, &payer, &payer, merkle_tree_keypair, config) .await .unwrap(); diff --git a/xtask/src/create_batch_state_tree.rs b/xtask/src/create_batch_state_tree.rs index 0cf7810b71..9d0c1e2be1 100644 --- a/xtask/src/create_batch_state_tree.rs +++ b/xtask/src/create_batch_state_tree.rs @@ -128,6 +128,7 @@ pub async fn create_batch_state_tree(options: Options) -> anyhow::Result<()> { let balance = rpc.get_balance(&payer.pubkey()).await.unwrap(); println!("Payer balance: {:?}", balance); let tx_hash = create_batched_state_merkle_tree( + &payer, &payer, true, &mut rpc, diff --git a/xtask/src/create_state_tree.rs b/xtask/src/create_state_tree.rs index e1b8a7b733..ef0fe83e05 100644 --- a/xtask/src/create_state_tree.rs +++ b/xtask/src/create_state_tree.rs @@ -140,6 +140,7 @@ pub async fn create_state_tree(options: Options) -> anyhow::Result<()> { let balance = rpc.get_balance(&payer.pubkey()).await.unwrap(); println!("Payer balance: {:?}", balance); let tx_hash = create_state_merkle_tree_and_queue_account( + &payer, &payer, true, &mut rpc, diff --git a/xtask/src/new_deployment.rs b/xtask/src/new_deployment.rs index 7de66cea3a..b5af0d100b 100644 --- a/xtask/src/new_deployment.rs +++ b/xtask/src/new_deployment.rs @@ -257,6 +257,7 @@ pub async fn init_new_deployment(options: Options) -> anyhow::Result<()> { ) .unwrap(); light_program_test::accounts::state_tree_v2::create_batched_state_merkle_tree( + &test_keypairs.governance_authority, &test_keypairs.governance_authority, true, &mut rpc, @@ -269,6 +270,7 @@ pub async fn init_new_deployment(options: Options) -> anyhow::Result<()> { light_program_test::accounts::address_tree_v2::create_batch_address_merkle_tree( &mut rpc, &test_keypairs.governance_authority, + &test_keypairs.governance_authority, &v2_address_mt, config.v2_address_tree_config.unwrap(), ) From f71c701df9da68d4b745128dfd924daec485cf5e Mon Sep 17 00:00:00 2001 From: ananas Date: Tue, 3 Mar 2026 17:32:15 +0000 Subject: [PATCH 2/2] chore: use bigger stack in test Entire-Checkpoint: ed8d74024c1f --- program-tests/justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program-tests/justfile b/program-tests/justfile index b305eec372..d65fa80df2 100644 --- a/program-tests/justfile +++ b/program-tests/justfile @@ -16,7 +16,7 @@ test-account-compression: RUSTFLAGS="-D warnings" cargo test-sbf -p account-compression-test test-registry: - RUSTFLAGS="-D warnings" cargo test-sbf -p registry-test + RUST_MIN_STACK=16777216 RUSTFLAGS="-D warnings" cargo test-sbf -p registry-test # System program tests test-system: test-system-address test-system-compression test-system-re-init