From 7166d1bbeaf6f819719d3a8af4687631ec5926dd Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sat, 8 Aug 2026 08:47:38 +0530 Subject: [PATCH 01/10] feat(fraud-proofs): Implement UpdateVerifierRegistry --- dlp-api/src/v2/args/mod.rs | 2 + .../src/v2/args/update_verifier_registry.rs | 25 ++ dlp-api/src/v2/instruction.rs | 2 + dlp-api/src/v2/instruction_builder/mod.rs | 2 + .../update_verifier_registry.rs | 40 +++ src/v2/processor/bootstrap/mod.rs | 2 + .../bootstrap/update_verifier_registry.rs | 155 ++++++++++ src/v2/processor/mod.rs | 3 + tests/test_v2_update_verifier_registry.rs | 279 ++++++++++++++++++ 9 files changed, 510 insertions(+) create mode 100644 dlp-api/src/v2/args/update_verifier_registry.rs create mode 100644 dlp-api/src/v2/instruction_builder/update_verifier_registry.rs create mode 100644 src/v2/processor/bootstrap/update_verifier_registry.rs create mode 100644 tests/test_v2_update_verifier_registry.rs diff --git a/dlp-api/src/v2/args/mod.rs b/dlp-api/src/v2/args/mod.rs index 9f8ca380..08637186 100644 --- a/dlp-api/src/v2/args/mod.rs +++ b/dlp-api/src/v2/args/mod.rs @@ -4,7 +4,9 @@ mod init_protocol_config; mod register_operator; mod register_verifier; +mod update_verifier_registry; pub use init_protocol_config::*; pub use register_operator::*; pub use register_verifier::*; +pub use update_verifier_registry::*; diff --git a/dlp-api/src/v2/args/update_verifier_registry.rs b/dlp-api/src/v2/args/update_verifier_registry.rs new file mode 100644 index 00000000..d9e810e9 --- /dev/null +++ b/dlp-api/src/v2/args/update_verifier_registry.rs @@ -0,0 +1,25 @@ +use wheels::{layout::Decodable, variable_offset_layout}; + +use crate::solana_program::program_error::ProgramError; + +pub const VERIFIER_REGISTRY_ACTION_ADD: u8 = 1; +pub const VERIFIER_REGISTRY_ACTION_REMOVE: u8 = 2; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[variable_offset_layout(buffer_offset = unaligned)] +pub struct UpdateVerifierRegistryArgs { + pub action: u8, + pub weight: u64, +} + +impl UpdateVerifierRegistryArgs { + pub fn try_from_bytes(data: &[u8]) -> Result { + let view = ::decode(data) + .map_err(super::super::state::layout_error_to_program_error)?; + + Ok(Self { + action: view.action(), + weight: view.weight(), + }) + } +} diff --git a/dlp-api/src/v2/instruction.rs b/dlp-api/src/v2/instruction.rs index b0e1f6df..a8944a32 100644 --- a/dlp-api/src/v2/instruction.rs +++ b/dlp-api/src/v2/instruction.rs @@ -11,6 +11,8 @@ pub enum DlpV2Instruction { RegisterOperator = 101, /// Registers one verifier and deposits its initial stake. RegisterVerifier = 102, + /// Updates the set of verifiers that can be selected. + UpdateVerifierRegistry = 103, } impl DlpV2Instruction { diff --git a/dlp-api/src/v2/instruction_builder/mod.rs b/dlp-api/src/v2/instruction_builder/mod.rs index 5d6f02ab..72621b82 100644 --- a/dlp-api/src/v2/instruction_builder/mod.rs +++ b/dlp-api/src/v2/instruction_builder/mod.rs @@ -1,7 +1,9 @@ mod init_protocol_config; mod register_operator; mod register_verifier; +mod update_verifier_registry; pub use init_protocol_config::*; pub use register_operator::*; pub use register_verifier::*; +pub use update_verifier_registry::*; diff --git a/dlp-api/src/v2/instruction_builder/update_verifier_registry.rs b/dlp-api/src/v2/instruction_builder/update_verifier_registry.rs new file mode 100644 index 00000000..30466fea --- /dev/null +++ b/dlp-api/src/v2/instruction_builder/update_verifier_registry.rs @@ -0,0 +1,40 @@ +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_sdk_ids::system_program; +use wheels::layout::Encodable; + +use crate::{ + compat::{Compatize, Modernize}, + v2::{ + pda::{protocol_config_pda, verifier_bond_pda, verifier_registry_pda}, + DlpV2Instruction, UpdateVerifierRegistryArgs, + }, +}; + +/// Builds the instruction that updates the verifier selection registry. +pub fn update_verifier_registry( + authority: Pubkey, + verifier: Pubkey, + args: UpdateVerifierRegistryArgs, +) -> Instruction { + Instruction { + program_id: crate::id().modernize(), + accounts: vec![ + AccountMeta::new(authority, true), + AccountMeta::new_readonly(protocol_config_pda().modernize(), false), + AccountMeta::new(verifier_registry_pda().modernize(), false), + AccountMeta::new_readonly( + verifier_bond_pda(&verifier.compatize()).modernize(), + false, + ), + AccountMeta::new_readonly(system_program::id(), false), + ], + data: [ + DlpV2Instruction::UpdateVerifierRegistry.to_vec(), + args.encode().unwrap(), + ] + .concat(), + } +} diff --git a/src/v2/processor/bootstrap/mod.rs b/src/v2/processor/bootstrap/mod.rs index 5d6f02ab..72621b82 100644 --- a/src/v2/processor/bootstrap/mod.rs +++ b/src/v2/processor/bootstrap/mod.rs @@ -1,7 +1,9 @@ mod init_protocol_config; mod register_operator; mod register_verifier; +mod update_verifier_registry; pub use init_protocol_config::*; pub use register_operator::*; pub use register_verifier::*; +pub use update_verifier_registry::*; diff --git a/src/v2/processor/bootstrap/update_verifier_registry.rs b/src/v2/processor/bootstrap/update_verifier_registry.rs new file mode 100644 index 00000000..cfa9414f --- /dev/null +++ b/src/v2/processor/bootstrap/update_verifier_registry.rs @@ -0,0 +1,155 @@ +use dlp_api::{ + error::DlpError, + v2::{ + pda::{ + PROTOCOL_CONFIG_SEED, VERIFIER_BOND_SEED, VERIFIER_REGISTRY_SEED, + }, + ProtocolConfig, UpdateVerifierRegistryArgs, VerifierBond, + VerifierRegistry, VerifierRegistryEntry, VERIFIER_REGISTRY_ACTION_ADD, + VERIFIER_STATUS_ACTIVE, + }, +}; +use solana_sdk_ids::system_program; + +use crate::{ + processor::utils::{ + loaders::{ + load_initialized_pda, load_owned_pda, load_program, load_signer, + }, + pda::resize_pda, + }, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, + }, +}; + +/// Update the verifier registry used by v2 verifier selection. +/// +/// Accounts: +/// 0: `[signer, writable]` protocol authority and registry rent payer +/// 1: `[]` ProtocolConfig PDA +/// 2: `[writable]` VerifierRegistry PDA +/// 3: `[]` VerifierBond PDA +/// 4: `[]` system program +pub fn process_update_verifier_registry( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let args = UpdateVerifierRegistryArgs::try_from_bytes(data)?; + + let [authority, protocol_config, verifier_registry, verifier_bond, system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + load_signer(authority, "authority")?; + load_program(system_program, system_program::id(), "system program")?; + + load_initialized_pda( + protocol_config, + &[PROTOCOL_CONFIG_SEED], + &crate::id(), + false, + "protocol config", + )?; + load_initialized_pda( + verifier_registry, + &[VERIFIER_REGISTRY_SEED], + &crate::id(), + true, + "verifier registry", + )?; + load_owned_pda(verifier_bond, &crate::id(), "verifier bond")?; + + let protocol_config_data = protocol_config.try_borrow_data()?; + let protocol_config_state = + ProtocolConfig::try_from_bytes_with_discriminator( + protocol_config_data.as_ref(), + )?; + if protocol_config_state.authority != *authority.key { + return Err(DlpError::InvalidAuthority.into()); + } + + let verifier_bond_data = verifier_bond.try_borrow_data()?; + let verifier_bond_state = VerifierBond::try_from_bytes_with_discriminator( + verifier_bond_data.as_ref(), + )?; + drop(verifier_bond_data); + + load_initialized_pda( + verifier_bond, + &[ + VERIFIER_BOND_SEED, + verifier_bond_state.verifier_identity.as_ref(), + ], + &crate::id(), + false, + "verifier bond", + )?; + + if args.action != VERIFIER_REGISTRY_ACTION_ADD { + // CHECKPOINT: implement `VERIFIER_REGISTRY_ACTION_REMOVE` when + // withdrawal/removal rules are finalized. + return Err(ProgramError::InvalidInstructionData); + } + + validate_add_args(&args, &protocol_config_state, &verifier_bond_state)?; + + let verifier_registry_data = verifier_registry.try_borrow_data()?; + let mut verifier_registry_state = + VerifierRegistry::try_from_bytes_with_discriminator( + verifier_registry_data.as_ref(), + )?; + drop(verifier_registry_data); + + if verifier_registry_state.entries.iter().any(|entry| { + entry.verifier_identity == verifier_bond_state.verifier_identity + || entry.verifier_bond == *verifier_bond.key + }) { + return Err(ProgramError::AccountAlreadyInitialized); + } + + verifier_registry_state.entries.push(VerifierRegistryEntry { + verifier_identity: verifier_bond_state.verifier_identity, + verifier_bond: *verifier_bond.key, + weight: args.weight, + }); + verifier_registry_state.registry_revision = verifier_registry_state + .registry_revision + .checked_add(1) + .ok_or(DlpError::Overflow)?; + + // CHECKPOINT: before registry size grows beyond bootstrap needs, define a + // max entry count or switch to a paged/Merkle registry. + resize_pda( + authority, + verifier_registry, + system_program, + verifier_registry_state.size_with_discriminator(), + )?; + + let mut verifier_registry_data = verifier_registry.try_borrow_mut_data()?; + verifier_registry_state + .to_bytes_with_discriminator(verifier_registry_data.as_mut())?; + + Ok(()) +} + +fn validate_add_args( + args: &UpdateVerifierRegistryArgs, + protocol_config: &ProtocolConfig, + verifier_bond: &VerifierBond, +) -> ProgramResult { + if args.weight == 0 + || verifier_bond.status != VERIFIER_STATUS_ACTIVE + || verifier_bond.stake_lamports < protocol_config.min_verifier_bond + || verifier_bond.withdraw_requested_slot.is_some() + { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(()) +} diff --git a/src/v2/processor/mod.rs b/src/v2/processor/mod.rs index fceada61..268fd5dc 100644 --- a/src/v2/processor/mod.rs +++ b/src/v2/processor/mod.rs @@ -22,5 +22,8 @@ pub fn process_instruction( DlpV2Instruction::RegisterVerifier => { process_register_verifier(accounts, data) } + DlpV2Instruction::UpdateVerifierRegistry => { + process_update_verifier_registry(program_id, accounts, data) + } } } diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs new file mode 100644 index 00000000..49677301 --- /dev/null +++ b/tests/test_v2_update_verifier_registry.rs @@ -0,0 +1,279 @@ +use dlp_api::v2::{ + instruction_builder::{register_verifier, update_verifier_registry}, + pda::{verifier_bond_pda, verifier_registry_pda}, + RegisterVerifierArgs, UpdateVerifierRegistryArgs, VerifierRegistry, + VERIFIER_REGISTRY_ACTION_ADD, VERIFIER_REGISTRY_ACTION_REMOVE, +}; +use solana_program::native_token::LAMPORTS_PER_SOL; +use solana_sdk::{ + signature::{Keypair, Signer}, + transaction::Transaction, +}; +use solana_system_interface::instruction as system_instruction; + +mod fixtures; + +use crate::fixtures::v2::{init_v2, setup_program_test_env, valid_args}; + +#[tokio::test] +async fn test_v2_update_verifier_registry_adds_verifier() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + let config_args = valid_args(); + let verifier = Keypair::new(); + + init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + register_v2_verifier( + &banks, + &payer, + &verifier, + &authority, + config_args.min_verifier_bond, + ) + .await; + + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_ADD, + weight: 1, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_ok()); + + let verifier_registry_account = banks + .get_account(verifier_registry_pda()) + .await + .unwrap() + .unwrap(); + let verifier_registry = + VerifierRegistry::try_from_bytes_with_discriminator( + &verifier_registry_account.data, + ) + .unwrap(); + + assert_eq!(verifier_registry.registry_revision, 1); + assert_eq!(verifier_registry.entries.len(), 1); + assert_eq!( + verifier_registry.entries[0].verifier_identity, + verifier.pubkey() + ); + assert_eq!( + verifier_registry.entries[0].verifier_bond, + verifier_bond_pda(&verifier.pubkey()) + ); + assert_eq!(verifier_registry.entries[0].weight, 1); +} + +#[tokio::test] +async fn test_v2_update_verifier_registry_fails_twice() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + let config_args = valid_args(); + let verifier = Keypair::new(); + + init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + register_v2_verifier( + &banks, + &payer, + &verifier, + &authority, + config_args.min_verifier_bond, + ) + .await; + add_verifier_to_registry(&banks, &payer, &verifier, &authority, 1).await; + + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_ADD, + weight: 1, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_update_verifier_registry_fails_with_wrong_authority() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + let config_args = valid_args(); + let verifier = Keypair::new(); + + init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + register_v2_verifier( + &banks, + &payer, + &verifier, + &authority, + config_args.min_verifier_bond, + ) + .await; + + let wrong_authority = Keypair::new(); + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + wrong_authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_ADD, + weight: 1, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &wrong_authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_update_verifier_registry_fails_with_zero_weight() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + let config_args = valid_args(); + let verifier = Keypair::new(); + + init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + register_v2_verifier( + &banks, + &payer, + &verifier, + &authority, + config_args.min_verifier_bond, + ) + .await; + + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_ADD, + weight: 0, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +#[tokio::test] +async fn test_v2_update_verifier_registry_fails_with_remove_action() { + let (banks, payer, authority, blockhash) = setup_program_test_env().await; + let config_args = valid_args(); + let verifier = Keypair::new(); + + init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + register_v2_verifier( + &banks, + &payer, + &verifier, + &authority, + config_args.min_verifier_bond, + ) + .await; + + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_REMOVE, + weight: 1, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); +} + +async fn register_v2_verifier( + banks: &solana_program_test::BanksClient, + payer: &Keypair, + verifier: &Keypair, + authority: &Keypair, + amount_lamports: u64, +) { + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = system_instruction::transfer( + &payer.pubkey(), + &verifier.pubkey(), + LAMPORTS_PER_SOL, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer], + blockhash, + ); + banks.process_transaction(tx).await.unwrap(); + + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = register_verifier( + verifier.pubkey(), + authority.pubkey(), + RegisterVerifierArgs { amount_lamports }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer, verifier, authority], + blockhash, + ); + + banks.process_transaction(tx).await.unwrap(); +} + +async fn add_verifier_to_registry( + banks: &solana_program_test::BanksClient, + payer: &Keypair, + verifier: &Keypair, + authority: &Keypair, + weight: u64, +) { + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_ADD, + weight, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer, authority], + blockhash, + ); + + banks.process_transaction(tx).await.unwrap(); +} From 3c19dccea3841d5e3b87100690f3f657cea6c3d2 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Mon, 24 Aug 2026 03:53:22 +0530 Subject: [PATCH 02/10] Use layout views for verifier registry updates --- .../src/v2/args/update_verifier_registry.rs | 18 +- src/processor/fast/utils/pda.rs | 24 ++ .../bootstrap/update_verifier_registry.rs | 221 +++++++++++------- tests/test_v2_update_verifier_registry.rs | 34 +-- 4 files changed, 182 insertions(+), 115 deletions(-) diff --git a/dlp-api/src/v2/args/update_verifier_registry.rs b/dlp-api/src/v2/args/update_verifier_registry.rs index d9e810e9..e000f60f 100644 --- a/dlp-api/src/v2/args/update_verifier_registry.rs +++ b/dlp-api/src/v2/args/update_verifier_registry.rs @@ -1,25 +1,11 @@ -use wheels::{layout::Decodable, variable_offset_layout}; - -use crate::solana_program::program_error::ProgramError; +use wheels::variable_offset_layout; pub const VERIFIER_REGISTRY_ACTION_ADD: u8 = 1; pub const VERIFIER_REGISTRY_ACTION_REMOVE: u8 = 2; #[derive(Clone, Debug, PartialEq, Eq)] -#[variable_offset_layout(buffer_offset = unaligned)] +#[variable_offset_layout(buffer_offset = 0)] pub struct UpdateVerifierRegistryArgs { pub action: u8, pub weight: u64, } - -impl UpdateVerifierRegistryArgs { - pub fn try_from_bytes(data: &[u8]) -> Result { - let view = ::decode(data) - .map_err(super::super::state::layout_error_to_program_error)?; - - Ok(Self { - action: view.action(), - weight: view.weight(), - }) - } -} diff --git a/src/processor/fast/utils/pda.rs b/src/processor/fast/utils/pda.rs index c08b67ed..c88a6e9b 100644 --- a/src/processor/fast/utils/pda.rs +++ b/src/processor/fast/utils/pda.rs @@ -80,6 +80,30 @@ pub(crate) fn close_pda( target_account.resize(0) } +/// Resizes an existing PDA and tops up rent when the account grows. +#[inline(always)] +pub(crate) fn resize_pda( + payer: &AccountView, + target_account: &AccountView, + space: usize, +) -> ProgramResult { + let rent = Rent::get()?; + let rent_exempt_balance = rent + .try_minimum_balance(space)? + .saturating_sub(target_account.lamports()); + + if rent_exempt_balance > 0 { + system::Transfer { + from: payer, + to: target_account, + lamports: rent_exempt_balance, + } + .invoke()?; + } + + target_account.resize(space) +} + /// Close PDA with fees, distributing the fees to the specified addresses in sequence /// The total fees are calculated as `fee_percentage` of the total lamports in the PDA /// Each fee address receives fee_percentage % of the previous fee address's amount diff --git a/src/v2/processor/bootstrap/update_verifier_registry.rs b/src/v2/processor/bootstrap/update_verifier_registry.rs index cfa9414f..0e702084 100644 --- a/src/v2/processor/bootstrap/update_verifier_registry.rs +++ b/src/v2/processor/bootstrap/update_verifier_registry.rs @@ -4,24 +4,24 @@ use dlp_api::{ pda::{ PROTOCOL_CONFIG_SEED, VERIFIER_BOND_SEED, VERIFIER_REGISTRY_SEED, }, - ProtocolConfig, UpdateVerifierRegistryArgs, VerifierBond, + ProtocolConfig, ProtocolConfigView, UpdateVerifierRegistryArgs, + UpdateVerifierRegistryArgsView, VerifierBond, VerifierBondView, VerifierRegistry, VerifierRegistryEntry, VERIFIER_REGISTRY_ACTION_ADD, VERIFIER_STATUS_ACTIVE, }, }; -use solana_sdk_ids::system_program; +use pinocchio::{ + address::Address, error::ProgramError, AccountView, ProgramResult, +}; +use wheels::{ + layout::{Decodable, Encodable}, + require_eq, require_eq_keys, require_ge, require_n_accounts, require_ne, + require_signer, +}; use crate::{ - processor::utils::{ - loaders::{ - load_initialized_pda, load_owned_pda, load_program, load_signer, - }, - pda::resize_pda, - }, - solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, - }, + processor::fast::utils::pda::resize_pda, + requires::{require_initialized_pda, require_owned_pda}, }; /// Update the verifier registry used by v2 verifier selection. @@ -31,125 +31,174 @@ use crate::{ /// 1: `[]` ProtocolConfig PDA /// 2: `[writable]` VerifierRegistry PDA /// 3: `[]` VerifierBond PDA -/// 4: `[]` system program +/// 4: `[]` system program, required by system CPI +#[inline(never)] pub fn process_update_verifier_registry( - _program_id: &Pubkey, - accounts: &[AccountInfo], + accounts: &[AccountView], data: &[u8], ) -> ProgramResult { - let args = UpdateVerifierRegistryArgs::try_from_bytes(data)?; + let [ + authority, // force multi-line + protocol_config, + verifier_registry, + verifier_bond, + _system_program, + ] = require_n_accounts!(accounts, 5); - let [authority, protocol_config, verifier_registry, verifier_bond, system_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; + let args = UpdateVerifierRegistryArgs::decode(data)?; - load_signer(authority, "authority")?; - load_program(system_program, system_program::id(), "system program")?; + require_signer!(authority); - load_initialized_pda( + require_initialized_pda( protocol_config, &[PROTOCOL_CONFIG_SEED], - &crate::id(), + &crate::fast::ID, false, "protocol config", )?; - load_initialized_pda( + require_initialized_pda( verifier_registry, &[VERIFIER_REGISTRY_SEED], - &crate::id(), + &crate::fast::ID, true, "verifier registry", )?; - load_owned_pda(verifier_bond, &crate::id(), "verifier bond")?; + require_owned_pda(verifier_bond, &crate::fast::ID, "verifier bond")?; - let protocol_config_data = protocol_config.try_borrow_data()?; + let protocol_config_data = protocol_config.try_borrow()?; let protocol_config_state = - ProtocolConfig::try_from_bytes_with_discriminator( - protocol_config_data.as_ref(), - )?; - if protocol_config_state.authority != *authority.key { - return Err(DlpError::InvalidAuthority.into()); - } + ProtocolConfig::decode(protocol_config_data.as_ref())?; + validate_protocol_config(&protocol_config_state, authority)?; - let verifier_bond_data = verifier_bond.try_borrow_data()?; - let verifier_bond_state = VerifierBond::try_from_bytes_with_discriminator( - verifier_bond_data.as_ref(), - )?; - drop(verifier_bond_data); + let verifier_bond_data = verifier_bond.try_borrow()?; + let verifier_bond_state = + VerifierBond::decode(verifier_bond_data.as_ref())?; + validate_verifier_bond(&verifier_bond_state, verifier_bond)?; - load_initialized_pda( - verifier_bond, - &[ - VERIFIER_BOND_SEED, - verifier_bond_state.verifier_identity.as_ref(), - ], - &crate::id(), - false, - "verifier bond", - )?; - - if args.action != VERIFIER_REGISTRY_ACTION_ADD { + if args.action() != VERIFIER_REGISTRY_ACTION_ADD { // CHECKPOINT: implement `VERIFIER_REGISTRY_ACTION_REMOVE` when // withdrawal/removal rules are finalized. return Err(ProgramError::InvalidInstructionData); } validate_add_args(&args, &protocol_config_state, &verifier_bond_state)?; + drop(protocol_config_data); - let verifier_registry_data = verifier_registry.try_borrow_data()?; - let mut verifier_registry_state = - VerifierRegistry::try_from_bytes_with_discriminator( - verifier_registry_data.as_ref(), - )?; - drop(verifier_registry_data); + let verifier_identity = *verifier_bond_state.verifier_identity(); + drop(verifier_bond_data); + + let verifier_registry_data = verifier_registry.try_borrow()?; + let verifier_registry_view = + VerifierRegistry::decode(verifier_registry_data.as_ref())?; + if verifier_registry_view.discriminator() != VerifierRegistry::DISCRIMINATOR + { + return Err(ProgramError::InvalidAccountData); + } - if verifier_registry_state.entries.iter().any(|entry| { - entry.verifier_identity == verifier_bond_state.verifier_identity - || entry.verifier_bond == *verifier_bond.key - }) { - return Err(ProgramError::AccountAlreadyInitialized); + let verifier_bond_key = verifier_bond.address().to_bytes().into(); + let mut entries = + Vec::with_capacity(verifier_registry_view.entries().len() + 1); + for entry in verifier_registry_view.entries().iter() { + if *entry.verifier_identity() == verifier_identity + || *entry.verifier_bond() == verifier_bond_key + { + return Err(ProgramError::AccountAlreadyInitialized); + } + + entries.push(VerifierRegistryEntry { + verifier_identity: *entry.verifier_identity(), + verifier_bond: *entry.verifier_bond(), + weight: entry.weight(), + }); } - verifier_registry_state.entries.push(VerifierRegistryEntry { - verifier_identity: verifier_bond_state.verifier_identity, - verifier_bond: *verifier_bond.key, - weight: args.weight, + entries.push(VerifierRegistryEntry { + verifier_identity, + verifier_bond: verifier_bond_key, + weight: args.weight(), }); - verifier_registry_state.registry_revision = verifier_registry_state - .registry_revision - .checked_add(1) - .ok_or(DlpError::Overflow)?; + + let updated_registry = VerifierRegistry { + discriminator: VerifierRegistry::DISCRIMINATOR, + registry_revision: verifier_registry_view + .registry_revision() + .checked_add(1) + .ok_or(DlpError::Overflow)?, + next_selection_index: verifier_registry_view.next_selection_index(), + entries, + }; + drop(verifier_registry_data); // CHECKPOINT: before registry size grows beyond bootstrap needs, define a // max entry count or switch to a paged/Merkle registry. resize_pda( authority, verifier_registry, - system_program, - verifier_registry_state.size_with_discriminator(), + updated_registry.encoded_len()?, )?; + updated_registry.encode_to(verifier_registry.try_borrow_mut()?.as_mut())?; - let mut verifier_registry_data = verifier_registry.try_borrow_mut_data()?; - verifier_registry_state - .to_bytes_with_discriminator(verifier_registry_data.as_mut())?; + Ok(()) +} + +fn validate_protocol_config( + protocol_config: &ProtocolConfigView<'_>, + authority: &AccountView, +) -> ProgramResult { + if protocol_config.discriminator() != ProtocolConfig::DISCRIMINATOR { + return Err(ProgramError::InvalidAccountData); + } + require_eq_keys!( + &Address::from(protocol_config.authority().to_bytes()), + authority.address(), + DlpError::InvalidAuthority + ); Ok(()) } -fn validate_add_args( - args: &UpdateVerifierRegistryArgs, - protocol_config: &ProtocolConfig, - verifier_bond: &VerifierBond, +fn validate_verifier_bond( + verifier_bond: &VerifierBondView<'_>, + verifier_bond_account: &AccountView, ) -> ProgramResult { - if args.weight == 0 - || verifier_bond.status != VERIFIER_STATUS_ACTIVE - || verifier_bond.stake_lamports < protocol_config.min_verifier_bond - || verifier_bond.withdraw_requested_slot.is_some() - { - return Err(ProgramError::InvalidInstructionData); + if verifier_bond.discriminator() != VerifierBond::DISCRIMINATOR { + return Err(ProgramError::InvalidAccountData); } + require_initialized_pda( + verifier_bond_account, + &[ + VERIFIER_BOND_SEED, + verifier_bond.verifier_identity().as_ref(), + ], + &crate::fast::ID, + false, + "verifier bond", + )?; + + Ok(()) +} + +fn validate_add_args( + args: &UpdateVerifierRegistryArgsView<'_>, + protocol_config: &ProtocolConfigView<'_>, + verifier_bond: &VerifierBondView<'_>, +) -> ProgramResult { + require_ne!(args.weight(), 0, ProgramError::InvalidInstructionData); + require_eq!( + verifier_bond.status(), + VERIFIER_STATUS_ACTIVE, + ProgramError::InvalidInstructionData + ); + require_ge!( + verifier_bond.stake_lamports(), + protocol_config.min_verifier_bond(), + ProgramError::InvalidInstructionData + ); + require_eq!( + verifier_bond.withdraw_requested_slot().is_none(), + true, + ProgramError::InvalidInstructionData + ); Ok(()) } diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index 49677301..d495fff2 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -5,11 +5,13 @@ use dlp_api::v2::{ VERIFIER_REGISTRY_ACTION_ADD, VERIFIER_REGISTRY_ACTION_REMOVE, }; use solana_program::native_token::LAMPORTS_PER_SOL; +use solana_program_test::ProgramTestBanksClientExt; use solana_sdk::{ signature::{Keypair, Signer}, transaction::Transaction, }; use solana_system_interface::instruction as system_instruction; +use wheels::layout::Decodable; mod fixtures; @@ -54,28 +56,30 @@ async fn test_v2_update_verifier_registry_adds_verifier() { .await .unwrap() .unwrap(); - let verifier_registry = - VerifierRegistry::try_from_bytes_with_discriminator( - &verifier_registry_account.data, - ) - .unwrap(); + let verifier_registry = ::decode( + &verifier_registry_account.data, + ) + .unwrap(); - assert_eq!(verifier_registry.registry_revision, 1); - assert_eq!(verifier_registry.entries.len(), 1); assert_eq!( - verifier_registry.entries[0].verifier_identity, - verifier.pubkey() + verifier_registry.discriminator(), + VerifierRegistry::DISCRIMINATOR ); + assert_eq!(verifier_registry.registry_revision(), 1); + assert_eq!(verifier_registry.entries().len(), 1); + let entry = verifier_registry.entries().iter().next().unwrap(); + assert_eq!(*entry.verifier_identity(), verifier.pubkey()); assert_eq!( - verifier_registry.entries[0].verifier_bond, + *entry.verifier_bond(), verifier_bond_pda(&verifier.pubkey()) ); - assert_eq!(verifier_registry.entries[0].weight, 1); + assert_eq!(entry.weight(), 1); } #[tokio::test] async fn test_v2_update_verifier_registry_fails_twice() { - let (banks, payer, authority, blockhash) = setup_program_test_env().await; + let (mut banks, payer, authority, blockhash) = + setup_program_test_env().await; let config_args = valid_args(); let verifier = Keypair::new(); @@ -90,7 +94,11 @@ async fn test_v2_update_verifier_registry_fails_twice() { .await; add_verifier_to_registry(&banks, &payer, &verifier, &authority, 1).await; - let blockhash = banks.get_latest_blockhash().await.unwrap(); + let latest_blockhash = banks.get_latest_blockhash().await.unwrap(); + let blockhash = banks + .get_new_latest_blockhash(&latest_blockhash) + .await + .unwrap(); let ix = update_verifier_registry( authority.pubkey(), verifier.pubkey(), From 235e9292cedc173046f86268b50bbe43829a5c33 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Mon, 24 Aug 2026 03:59:00 +0530 Subject: [PATCH 03/10] Dispatch verifier registry update without program id --- src/v2/processor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/processor/mod.rs b/src/v2/processor/mod.rs index 268fd5dc..3b4f3ea3 100644 --- a/src/v2/processor/mod.rs +++ b/src/v2/processor/mod.rs @@ -23,7 +23,7 @@ pub fn process_instruction( process_register_verifier(accounts, data) } DlpV2Instruction::UpdateVerifierRegistry => { - process_update_verifier_registry(program_id, accounts, data) + process_update_verifier_registry(accounts, data) } } } From f873d41acd30ac4dbb0a3df0ae0aae14ea153b5e Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Fri, 28 Aug 2026 12:36:58 +0530 Subject: [PATCH 04/10] Use one-byte tag offset for verifier registry args --- .../src/v2/args/update_verifier_registry.rs | 2 +- tests/test_v2_update_verifier_registry.rs | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/dlp-api/src/v2/args/update_verifier_registry.rs b/dlp-api/src/v2/args/update_verifier_registry.rs index e000f60f..36f2b25c 100644 --- a/dlp-api/src/v2/args/update_verifier_registry.rs +++ b/dlp-api/src/v2/args/update_verifier_registry.rs @@ -4,7 +4,7 @@ pub const VERIFIER_REGISTRY_ACTION_ADD: u8 = 1; pub const VERIFIER_REGISTRY_ACTION_REMOVE: u8 = 2; #[derive(Clone, Debug, PartialEq, Eq)] -#[variable_offset_layout(buffer_offset = 0)] +#[variable_offset_layout(buffer_offset = 1)] pub struct UpdateVerifierRegistryArgs { pub action: u8, pub weight: u64, diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index d495fff2..1d35b90c 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -1,22 +1,48 @@ use dlp_api::v2::{ instruction_builder::{register_verifier, update_verifier_registry}, pda::{verifier_bond_pda, verifier_registry_pda}, - RegisterVerifierArgs, UpdateVerifierRegistryArgs, VerifierRegistry, - VERIFIER_REGISTRY_ACTION_ADD, VERIFIER_REGISTRY_ACTION_REMOVE, + DlpV2Instruction, RegisterVerifierArgs, UpdateVerifierRegistryArgs, + VerifierRegistry, VERIFIER_REGISTRY_ACTION_ADD, + VERIFIER_REGISTRY_ACTION_REMOVE, }; use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program_test::ProgramTestBanksClientExt; use solana_sdk::{ + pubkey::Pubkey, signature::{Keypair, Signer}, transaction::Transaction, }; use solana_system_interface::instruction as system_instruction; -use wheels::layout::Decodable; +use wheels::layout::{Decodable, Encodable}; mod fixtures; use crate::fixtures::v2::{init_v2, setup_program_test_env, valid_args}; +#[test] +fn test_v2_update_verifier_registry_instruction_data_uses_one_byte_tag() { + let args = UpdateVerifierRegistryArgs { + action: VERIFIER_REGISTRY_ACTION_ADD, + weight: 7, + }; + let ix = update_verifier_registry( + Pubkey::new_unique(), + Pubkey::new_unique(), + args.clone(), + ); + let encoded_args = args.encode().unwrap(); + + assert_eq!(ix.data[0], DlpV2Instruction::UpdateVerifierRegistry as u8); + assert_eq!(ix.data.len(), 1 + encoded_args.len()); + assert_eq!(&ix.data[1..], encoded_args.as_slice()); + + let decoded = + ::decode(&ix.data[1..]) + .unwrap(); + assert_eq!(decoded.action(), args.action); + assert_eq!(decoded.weight(), args.weight); +} + #[tokio::test] async fn test_v2_update_verifier_registry_adds_verifier() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; From e163ebb085ebe898434bac2c660c8b5a0a08cf7c Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sun, 30 Aug 2026 21:54:57 +0530 Subject: [PATCH 05/10] Share PDA rent top-up helper --- src/processor/fast/utils/pda.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/processor/fast/utils/pda.rs b/src/processor/fast/utils/pda.rs index c88a6e9b..cd89ad1e 100644 --- a/src/processor/fast/utils/pda.rs +++ b/src/processor/fast/utils/pda.rs @@ -86,6 +86,18 @@ pub(crate) fn resize_pda( payer: &AccountView, target_account: &AccountView, space: usize, +) -> ProgramResult { + top_up_pda_rent(payer, target_account, space)?; + + target_account.resize(space) +} + +/// Tops up a PDA to the rent-exempt balance for `space`. +#[inline(always)] +pub(crate) fn top_up_pda_rent( + payer: &AccountView, + target_account: &AccountView, + space: usize, ) -> ProgramResult { let rent = Rent::get()?; let rent_exempt_balance = rent @@ -101,7 +113,7 @@ pub(crate) fn resize_pda( .invoke()?; } - target_account.resize(space) + Ok(()) } /// Close PDA with fees, distributing the fees to the specified addresses in sequence From 50ba87bcccc88df668f40b218609c5bc4ab8a160 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Mon, 31 Aug 2026 13:03:28 +0530 Subject: [PATCH 06/10] Drop trivial UpdateVerifierRegistry instruction data test --- tests/test_v2_update_verifier_registry.rs | 32 +++-------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index 1d35b90c..d495fff2 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -1,48 +1,22 @@ use dlp_api::v2::{ instruction_builder::{register_verifier, update_verifier_registry}, pda::{verifier_bond_pda, verifier_registry_pda}, - DlpV2Instruction, RegisterVerifierArgs, UpdateVerifierRegistryArgs, - VerifierRegistry, VERIFIER_REGISTRY_ACTION_ADD, - VERIFIER_REGISTRY_ACTION_REMOVE, + RegisterVerifierArgs, UpdateVerifierRegistryArgs, VerifierRegistry, + VERIFIER_REGISTRY_ACTION_ADD, VERIFIER_REGISTRY_ACTION_REMOVE, }; use solana_program::native_token::LAMPORTS_PER_SOL; use solana_program_test::ProgramTestBanksClientExt; use solana_sdk::{ - pubkey::Pubkey, signature::{Keypair, Signer}, transaction::Transaction, }; use solana_system_interface::instruction as system_instruction; -use wheels::layout::{Decodable, Encodable}; +use wheels::layout::Decodable; mod fixtures; use crate::fixtures::v2::{init_v2, setup_program_test_env, valid_args}; -#[test] -fn test_v2_update_verifier_registry_instruction_data_uses_one_byte_tag() { - let args = UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_ADD, - weight: 7, - }; - let ix = update_verifier_registry( - Pubkey::new_unique(), - Pubkey::new_unique(), - args.clone(), - ); - let encoded_args = args.encode().unwrap(); - - assert_eq!(ix.data[0], DlpV2Instruction::UpdateVerifierRegistry as u8); - assert_eq!(ix.data.len(), 1 + encoded_args.len()); - assert_eq!(&ix.data[1..], encoded_args.as_slice()); - - let decoded = - ::decode(&ix.data[1..]) - .unwrap(); - assert_eq!(decoded.action(), args.action); - assert_eq!(decoded.weight(), args.weight); -} - #[tokio::test] async fn test_v2_update_verifier_registry_adds_verifier() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; From 886eb45e3ff903b7dbf6b4d02e76c97363470e0b Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Tue, 1 Sep 2026 05:21:05 +0530 Subject: [PATCH 07/10] Rename verifier registry update tests --- tests/test_v2_update_verifier_registry.rs | 70 +++++++++++++++++------ 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index d495fff2..925f13f1 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -15,15 +15,25 @@ use wheels::layout::Decodable; mod fixtures; -use crate::fixtures::v2::{init_v2, setup_program_test_env, valid_args}; +use crate::fixtures::v2::{ + initialize_protocol_config, setup_program_test_env, + valid_protocol_config_args, +}; #[tokio::test] -async fn test_v2_update_verifier_registry_adds_verifier() { +async fn test_update_verifier_registry_adds_verifier() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let config_args = valid_args(); + let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); - init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + initialize_protocol_config( + &banks, + &payer, + &authority, + blockhash, + config_args.clone(), + ) + .await; register_v2_verifier( &banks, &payer, @@ -77,13 +87,20 @@ async fn test_v2_update_verifier_registry_adds_verifier() { } #[tokio::test] -async fn test_v2_update_verifier_registry_fails_twice() { +async fn test_update_verifier_registry_fails_twice() { let (mut banks, payer, authority, blockhash) = setup_program_test_env().await; - let config_args = valid_args(); + let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); - init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + initialize_protocol_config( + &banks, + &payer, + &authority, + blockhash, + config_args.clone(), + ) + .await; register_v2_verifier( &banks, &payer, @@ -118,12 +135,19 @@ async fn test_v2_update_verifier_registry_fails_twice() { } #[tokio::test] -async fn test_v2_update_verifier_registry_fails_with_wrong_authority() { +async fn test_update_verifier_registry_fails_with_wrong_authority() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let config_args = valid_args(); + let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); - init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + initialize_protocol_config( + &banks, + &payer, + &authority, + blockhash, + config_args.clone(), + ) + .await; register_v2_verifier( &banks, &payer, @@ -154,12 +178,19 @@ async fn test_v2_update_verifier_registry_fails_with_wrong_authority() { } #[tokio::test] -async fn test_v2_update_verifier_registry_fails_with_zero_weight() { +async fn test_update_verifier_registry_fails_with_zero_weight() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let config_args = valid_args(); + let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); - init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + initialize_protocol_config( + &banks, + &payer, + &authority, + blockhash, + config_args.clone(), + ) + .await; register_v2_verifier( &banks, &payer, @@ -189,12 +220,19 @@ async fn test_v2_update_verifier_registry_fails_with_zero_weight() { } #[tokio::test] -async fn test_v2_update_verifier_registry_fails_with_remove_action() { +async fn test_update_verifier_registry_fails_with_remove_action() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; - let config_args = valid_args(); + let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); - init_v2(&banks, &payer, &authority, blockhash, config_args.clone()).await; + initialize_protocol_config( + &banks, + &payer, + &authority, + blockhash, + config_args.clone(), + ) + .await; register_v2_verifier( &banks, &payer, From d7864284b84a75dd1f75dcd6a13579fb7f00ef16 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 2 Sep 2026 14:56:55 +0530 Subject: [PATCH 08/10] dev review --- .../src/v2/args/update_verifier_registry.rs | 16 +- src/processor/fast/utils/pda.rs | 12 -- .../bootstrap/update_verifier_registry.rs | 175 +++++++++--------- tests/test_v2_update_verifier_registry.rs | 85 +++++---- 4 files changed, 158 insertions(+), 130 deletions(-) diff --git a/dlp-api/src/v2/args/update_verifier_registry.rs b/dlp-api/src/v2/args/update_verifier_registry.rs index 36f2b25c..94765988 100644 --- a/dlp-api/src/v2/args/update_verifier_registry.rs +++ b/dlp-api/src/v2/args/update_verifier_registry.rs @@ -1,11 +1,21 @@ use wheels::variable_offset_layout; -pub const VERIFIER_REGISTRY_ACTION_ADD: u8 = 1; -pub const VERIFIER_REGISTRY_ACTION_REMOVE: u8 = 2; - #[derive(Clone, Debug, PartialEq, Eq)] #[variable_offset_layout(buffer_offset = 1)] pub struct UpdateVerifierRegistryArgs { pub action: u8, pub weight: u64, } + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VerifierRegistryAction { + Add = 1, + Remove = 2, +} + +impl VerifierRegistryAction { + pub const fn value(self) -> u8 { + self as u8 + } +} diff --git a/src/processor/fast/utils/pda.rs b/src/processor/fast/utils/pda.rs index cd89ad1e..08b4e1ec 100644 --- a/src/processor/fast/utils/pda.rs +++ b/src/processor/fast/utils/pda.rs @@ -80,18 +80,6 @@ pub(crate) fn close_pda( target_account.resize(0) } -/// Resizes an existing PDA and tops up rent when the account grows. -#[inline(always)] -pub(crate) fn resize_pda( - payer: &AccountView, - target_account: &AccountView, - space: usize, -) -> ProgramResult { - top_up_pda_rent(payer, target_account, space)?; - - target_account.resize(space) -} - /// Tops up a PDA to the rent-exempt balance for `space`. #[inline(always)] pub(crate) fn top_up_pda_rent( diff --git a/src/v2/processor/bootstrap/update_verifier_registry.rs b/src/v2/processor/bootstrap/update_verifier_registry.rs index 0e702084..00be44a0 100644 --- a/src/v2/processor/bootstrap/update_verifier_registry.rs +++ b/src/v2/processor/bootstrap/update_verifier_registry.rs @@ -6,22 +6,19 @@ use dlp_api::{ }, ProtocolConfig, ProtocolConfigView, UpdateVerifierRegistryArgs, UpdateVerifierRegistryArgsView, VerifierBond, VerifierBondView, - VerifierRegistry, VerifierRegistryEntry, VERIFIER_REGISTRY_ACTION_ADD, - VERIFIER_STATUS_ACTIVE, + VerifierRegistry, VerifierRegistryAction, VerifierRegistryEntry, + VerifierStatus, }, }; -use pinocchio::{ - address::Address, error::ProgramError, AccountView, ProgramResult, -}; +use pinocchio::{error::ProgramError, AccountView, ProgramResult}; use wheels::{ - layout::{Decodable, Encodable}, - require_eq, require_eq_keys, require_ge, require_n_accounts, require_ne, - require_signer, + layout::Decodable, require, require_eq, require_eq_keys, require_ge, + require_n_accounts, require_signer, }; use crate::{ - processor::fast::utils::pda::resize_pda, - requires::{require_initialized_pda, require_owned_pda}, + processor::fast::utils::pda::top_up_pda_rent, + requires::{require_initialized_pda, require_owned_pda, require_pda}, }; /// Update the verifier registry used by v2 verifier selection. @@ -45,10 +42,11 @@ pub fn process_update_verifier_registry( _system_program, ] = require_n_accounts!(accounts, 5); - let args = UpdateVerifierRegistryArgs::decode(data)?; - require_signer!(authority); + let args = UpdateVerifierRegistryArgs::decode(data)?; + validate_update_args(&args)?; + require_initialized_pda( protocol_config, &[PROTOCOL_CONFIG_SEED], @@ -65,78 +63,88 @@ pub fn process_update_verifier_registry( )?; require_owned_pda(verifier_bond, &crate::fast::ID, "verifier bond")?; - let protocol_config_data = protocol_config.try_borrow()?; - let protocol_config_state = - ProtocolConfig::decode(protocol_config_data.as_ref())?; - validate_protocol_config(&protocol_config_state, authority)?; - let verifier_bond_data = verifier_bond.try_borrow()?; let verifier_bond_state = VerifierBond::decode(verifier_bond_data.as_ref())?; validate_verifier_bond(&verifier_bond_state, verifier_bond)?; - if args.action() != VERIFIER_REGISTRY_ACTION_ADD { - // CHECKPOINT: implement `VERIFIER_REGISTRY_ACTION_REMOVE` when - // withdrawal/removal rules are finalized. - return Err(ProgramError::InvalidInstructionData); - } - - validate_add_args(&args, &protocol_config_state, &verifier_bond_state)?; - drop(protocol_config_data); - - let verifier_identity = *verifier_bond_state.verifier_identity(); - drop(verifier_bond_data); - - let verifier_registry_data = verifier_registry.try_borrow()?; - let verifier_registry_view = - VerifierRegistry::decode(verifier_registry_data.as_ref())?; - if verifier_registry_view.discriminator() != VerifierRegistry::DISCRIMINATOR { - return Err(ProgramError::InvalidAccountData); - } - - let verifier_bond_key = verifier_bond.address().to_bytes().into(); - let mut entries = - Vec::with_capacity(verifier_registry_view.entries().len() + 1); - for entry in verifier_registry_view.entries().iter() { - if *entry.verifier_identity() == verifier_identity - || *entry.verifier_bond() == verifier_bond_key - { - return Err(ProgramError::AccountAlreadyInitialized); - } - - entries.push(VerifierRegistryEntry { - verifier_identity: *entry.verifier_identity(), - verifier_bond: *entry.verifier_bond(), - weight: entry.weight(), - }); + let protocol_config_data = protocol_config.try_borrow()?; + let protocol_config_state = + ProtocolConfig::decode(protocol_config_data.as_ref())?; + validate_protocol_config(&protocol_config_state, authority)?; + validate_verifier_can_be_added( + &protocol_config_state, + &verifier_bond_state, + )?; } - entries.push(VerifierRegistryEntry { - verifier_identity, - verifier_bond: verifier_bond_key, - weight: args.weight(), - }); - - let updated_registry = VerifierRegistry { - discriminator: VerifierRegistry::DISCRIMINATOR, - registry_revision: verifier_registry_view + let verifier_identity = verifier_bond_state.verifier_identity(); + let verifier_bond_key = verifier_bond.address(); + let registry_revision = { + let verifier_registry_data = verifier_registry.try_borrow()?; + let verifier_registry_view = + VerifierRegistry::decode(verifier_registry_data.as_ref())?; + require!( + verifier_registry_view.discriminator() + == VerifierRegistry::DISCRIMINATOR, + ProgramError::InvalidAccountData + ); + // CHECKPOINT: this treats verifier identity and verifier bond as + // separate unique registry keys. Revisit if bond rotation should keep + // the same identity entry instead of rejecting either duplicate. + require!( + !verifier_registry_view.entries().iter().any(|entry| { + entry.verifier_identity() == verifier_identity + || entry.verifier_bond() == verifier_bond_key + }), + ProgramError::AccountAlreadyInitialized + ); + + verifier_registry_view .registry_revision() .checked_add(1) - .ok_or(DlpError::Overflow)?, - next_selection_index: verifier_registry_view.next_selection_index(), - entries, + .ok_or(DlpError::Overflow)? }; - drop(verifier_registry_data); // CHECKPOINT: before registry size grows beyond bootstrap needs, define a // max entry count or switch to a paged/Merkle registry. - resize_pda( - authority, - verifier_registry, - updated_registry.encoded_len()?, - )?; - updated_registry.encode_to(verifier_registry.try_borrow_mut()?.as_mut())?; + let old_registry_len = verifier_registry.data_len(); + let mut verifier_registry_state = + VerifierRegistry::decode_mut(verifier_registry)?; + + verifier_registry_state + .entries_mut()? + .push(&VerifierRegistryEntry { + verifier_identity: *verifier_identity, + verifier_bond: *verifier_bond_key, + weight: args.weight(), + })?; + + verifier_registry_state + .registry_revision_mut()? + .set(registry_revision)?; + + let new_registry_len = verifier_registry.data_len(); + if new_registry_len > old_registry_len { + top_up_pda_rent(authority, verifier_registry, new_registry_len)?; + } + + Ok(()) +} + +fn validate_update_args( + args: &UpdateVerifierRegistryArgsView<'_>, +) -> ProgramResult { + // CHECKPOINT: implement `VerifierRegistryAction::Remove` when + // withdrawal/removal rules are finalized. + require!( + args.action() == VerifierRegistryAction::Add.value(), + ProgramError::InvalidInstructionData + ); + // MVP verifier selection is equal-weight round-robin, so the only + // meaningful weight until weighted selection exists is 1. + require_eq!(args.weight(), 1_u64, ProgramError::InvalidInstructionData); Ok(()) } @@ -145,11 +153,12 @@ fn validate_protocol_config( protocol_config: &ProtocolConfigView<'_>, authority: &AccountView, ) -> ProgramResult { - if protocol_config.discriminator() != ProtocolConfig::DISCRIMINATOR { - return Err(ProgramError::InvalidAccountData); - } + require!( + protocol_config.discriminator() == ProtocolConfig::DISCRIMINATOR, + ProgramError::InvalidAccountData + ); require_eq_keys!( - &Address::from(protocol_config.authority().to_bytes()), + protocol_config.authority(), authority.address(), DlpError::InvalidAuthority ); @@ -161,10 +170,11 @@ fn validate_verifier_bond( verifier_bond: &VerifierBondView<'_>, verifier_bond_account: &AccountView, ) -> ProgramResult { - if verifier_bond.discriminator() != VerifierBond::DISCRIMINATOR { - return Err(ProgramError::InvalidAccountData); - } - require_initialized_pda( + require!( + verifier_bond.discriminator() == VerifierBond::DISCRIMINATOR, + ProgramError::InvalidAccountData + ); + require_pda( verifier_bond_account, &[ VERIFIER_BOND_SEED, @@ -178,15 +188,13 @@ fn validate_verifier_bond( Ok(()) } -fn validate_add_args( - args: &UpdateVerifierRegistryArgsView<'_>, +fn validate_verifier_can_be_added( protocol_config: &ProtocolConfigView<'_>, verifier_bond: &VerifierBondView<'_>, ) -> ProgramResult { - require_ne!(args.weight(), 0, ProgramError::InvalidInstructionData); require_eq!( verifier_bond.status(), - VERIFIER_STATUS_ACTIVE, + VerifierStatus::Active.value(), ProgramError::InvalidInstructionData ); require_ge!( @@ -194,9 +202,8 @@ fn validate_add_args( protocol_config.min_verifier_bond(), ProgramError::InvalidInstructionData ); - require_eq!( + require!( verifier_bond.withdraw_requested_slot().is_none(), - true, ProgramError::InvalidInstructionData ); diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index 925f13f1..85c91d5f 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -1,10 +1,10 @@ use dlp_api::v2::{ instruction_builder::{register_verifier, update_verifier_registry}, - pda::{verifier_bond_pda, verifier_registry_pda}, + pda::{verifier_bond_pda, verifier_registry_pda, VERIFIER_REGISTRY_SEED}, RegisterVerifierArgs, UpdateVerifierRegistryArgs, VerifierRegistry, - VERIFIER_REGISTRY_ACTION_ADD, VERIFIER_REGISTRY_ACTION_REMOVE, + VerifierRegistryAction, }; -use solana_program::native_token::LAMPORTS_PER_SOL; +use solana_program::{native_token::LAMPORTS_PER_SOL, pubkey::Pubkey}; use solana_program_test::ProgramTestBanksClientExt; use solana_sdk::{ signature::{Keypair, Signer}, @@ -25,6 +25,8 @@ async fn test_update_verifier_registry_adds_verifier() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); + let (_, expected_verifier_registry_bump) = + Pubkey::find_program_address(&[VERIFIER_REGISTRY_SEED], &dlp_api::id()); initialize_protocol_config( &banks, @@ -48,7 +50,7 @@ async fn test_update_verifier_registry_adds_verifier() { authority.pubkey(), verifier.pubkey(), UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_ADD, + action: VerifierRegistryAction::Add.value(), weight: 1, }, ); @@ -66,15 +68,14 @@ async fn test_update_verifier_registry_adds_verifier() { .await .unwrap() .unwrap(); - let verifier_registry = ::decode( - &verifier_registry_account.data, - ) - .unwrap(); + let verifier_registry = + VerifierRegistry::decode(&verifier_registry_account.data).unwrap(); assert_eq!( verifier_registry.discriminator(), VerifierRegistry::DISCRIMINATOR ); + assert_eq!(verifier_registry.bump(), expected_verifier_registry_bump); assert_eq!(verifier_registry.registry_revision(), 1); assert_eq!(verifier_registry.entries().len(), 1); let entry = verifier_registry.entries().iter().next().unwrap(); @@ -120,7 +121,7 @@ async fn test_update_verifier_registry_fails_twice() { authority.pubkey(), verifier.pubkey(), UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_ADD, + action: VerifierRegistryAction::Add.value(), weight: 1, }, ); @@ -163,7 +164,7 @@ async fn test_update_verifier_registry_fails_with_wrong_authority() { wrong_authority.pubkey(), verifier.pubkey(), UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_ADD, + action: VerifierRegistryAction::Add.value(), weight: 1, }, ); @@ -178,7 +179,7 @@ async fn test_update_verifier_registry_fails_with_wrong_authority() { } #[tokio::test] -async fn test_update_verifier_registry_fails_with_zero_weight() { +async fn test_update_verifier_registry_fails_with_invalid_weight() { let (banks, payer, authority, blockhash) = setup_program_test_env().await; let config_args = valid_protocol_config_args(); let verifier = Keypair::new(); @@ -200,23 +201,45 @@ async fn test_update_verifier_registry_fails_with_zero_weight() { ) .await; - let blockhash = banks.get_latest_blockhash().await.unwrap(); - let ix = update_verifier_registry( - authority.pubkey(), - verifier.pubkey(), - UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_ADD, - weight: 0, - }, - ); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&payer.pubkey()), - &[&payer, &authority], - blockhash, - ); + { + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VerifierRegistryAction::Add.value(), + weight: 0, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); - assert!(banks.process_transaction(tx).await.is_err()); + assert!(banks.process_transaction(tx).await.is_err()); + } + + { + let blockhash = banks.get_latest_blockhash().await.unwrap(); + let ix = update_verifier_registry( + authority.pubkey(), + verifier.pubkey(), + UpdateVerifierRegistryArgs { + action: VerifierRegistryAction::Add.value(), + weight: 2, + }, + ); + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer, &authority], + blockhash, + ); + + assert!(banks.process_transaction(tx).await.is_err()); + } } #[tokio::test] @@ -247,7 +270,7 @@ async fn test_update_verifier_registry_fails_with_remove_action() { authority.pubkey(), verifier.pubkey(), UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_REMOVE, + action: VerifierRegistryAction::Remove.value(), weight: 1, }, ); @@ -266,7 +289,7 @@ async fn register_v2_verifier( payer: &Keypair, verifier: &Keypair, authority: &Keypair, - amount_lamports: u64, + stake_lamports: u64, ) { let blockhash = banks.get_latest_blockhash().await.unwrap(); let ix = system_instruction::transfer( @@ -286,7 +309,7 @@ async fn register_v2_verifier( let ix = register_verifier( verifier.pubkey(), authority.pubkey(), - RegisterVerifierArgs { amount_lamports }, + RegisterVerifierArgs { stake_lamports }, ); let tx = Transaction::new_signed_with_payer( &[ix], @@ -310,7 +333,7 @@ async fn add_verifier_to_registry( authority.pubkey(), verifier.pubkey(), UpdateVerifierRegistryArgs { - action: VERIFIER_REGISTRY_ACTION_ADD, + action: VerifierRegistryAction::Add.value(), weight, }, ); From 2d709b29ce86e1b834ab5b408b0eee786954e2a3 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 2 Sep 2026 15:52:51 +0530 Subject: [PATCH 09/10] Remove verifier registry revision update --- .../bootstrap/update_verifier_registry.rs | 14 +++----------- tests/test_v2_update_verifier_registry.rs | 1 - 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/v2/processor/bootstrap/update_verifier_registry.rs b/src/v2/processor/bootstrap/update_verifier_registry.rs index 00be44a0..c205f7f9 100644 --- a/src/v2/processor/bootstrap/update_verifier_registry.rs +++ b/src/v2/processor/bootstrap/update_verifier_registry.rs @@ -81,7 +81,8 @@ pub fn process_update_verifier_registry( let verifier_identity = verifier_bond_state.verifier_identity(); let verifier_bond_key = verifier_bond.address(); - let registry_revision = { + + { let verifier_registry_data = verifier_registry.try_borrow()?; let verifier_registry_view = VerifierRegistry::decode(verifier_registry_data.as_ref())?; @@ -100,12 +101,7 @@ pub fn process_update_verifier_registry( }), ProgramError::AccountAlreadyInitialized ); - - verifier_registry_view - .registry_revision() - .checked_add(1) - .ok_or(DlpError::Overflow)? - }; + } // CHECKPOINT: before registry size grows beyond bootstrap needs, define a // max entry count or switch to a paged/Merkle registry. @@ -121,10 +117,6 @@ pub fn process_update_verifier_registry( weight: args.weight(), })?; - verifier_registry_state - .registry_revision_mut()? - .set(registry_revision)?; - let new_registry_len = verifier_registry.data_len(); if new_registry_len > old_registry_len { top_up_pda_rent(authority, verifier_registry, new_registry_len)?; diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index 85c91d5f..7e2e0a02 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -76,7 +76,6 @@ async fn test_update_verifier_registry_adds_verifier() { VerifierRegistry::DISCRIMINATOR ); assert_eq!(verifier_registry.bump(), expected_verifier_registry_bump); - assert_eq!(verifier_registry.registry_revision(), 1); assert_eq!(verifier_registry.entries().len(), 1); let entry = verifier_registry.entries().iter().next().unwrap(); assert_eq!(*entry.verifier_identity(), verifier.pubkey()); From 9185361aed1823a3892b4bd2f5d71d9eb1f26e4d Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 2 Sep 2026 16:28:22 +0530 Subject: [PATCH 10/10] dev review 2 --- .../processor/bootstrap/update_verifier_registry.rs | 5 +++-- tests/test_v2_update_verifier_registry.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/v2/processor/bootstrap/update_verifier_registry.rs b/src/v2/processor/bootstrap/update_verifier_registry.rs index c205f7f9..97669d1a 100644 --- a/src/v2/processor/bootstrap/update_verifier_registry.rs +++ b/src/v2/processor/bootstrap/update_verifier_registry.rs @@ -103,8 +103,9 @@ pub fn process_update_verifier_registry( ); } - // CHECKPOINT: before registry size grows beyond bootstrap needs, define a - // max entry count or switch to a paged/Merkle registry. + // CHECKPOINT: this single-PDA Vec is only suitable while the verifier set + // is small. Before allowing unbounded growth, cap the entry count or + // replace this with paged storage / Merkle-root based membership. let old_registry_len = verifier_registry.data_len(); let mut verifier_registry_state = VerifierRegistry::decode_mut(verifier_registry)?; diff --git a/tests/test_v2_update_verifier_registry.rs b/tests/test_v2_update_verifier_registry.rs index 7e2e0a02..f6a61a55 100644 --- a/tests/test_v2_update_verifier_registry.rs +++ b/tests/test_v2_update_verifier_registry.rs @@ -36,7 +36,7 @@ async fn test_update_verifier_registry_adds_verifier() { config_args.clone(), ) .await; - register_v2_verifier( + fund_and_register_verifier( &banks, &payer, &verifier, @@ -101,7 +101,7 @@ async fn test_update_verifier_registry_fails_twice() { config_args.clone(), ) .await; - register_v2_verifier( + fund_and_register_verifier( &banks, &payer, &verifier, @@ -148,7 +148,7 @@ async fn test_update_verifier_registry_fails_with_wrong_authority() { config_args.clone(), ) .await; - register_v2_verifier( + fund_and_register_verifier( &banks, &payer, &verifier, @@ -191,7 +191,7 @@ async fn test_update_verifier_registry_fails_with_invalid_weight() { config_args.clone(), ) .await; - register_v2_verifier( + fund_and_register_verifier( &banks, &payer, &verifier, @@ -255,7 +255,7 @@ async fn test_update_verifier_registry_fails_with_remove_action() { config_args.clone(), ) .await; - register_v2_verifier( + fund_and_register_verifier( &banks, &payer, &verifier, @@ -283,7 +283,7 @@ async fn test_update_verifier_registry_fails_with_remove_action() { assert!(banks.process_transaction(tx).await.is_err()); } -async fn register_v2_verifier( +async fn fund_and_register_verifier( banks: &solana_program_test::BanksClient, payer: &Keypair, verifier: &Keypair,