diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c8d3569b..1d78ca8599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ All notable changes to this project will be documented in this file. - Add onchain parent DZD discovery to geoprobe-agent: periodically queries the Geolocation program for this probe's parent devices and resolves their metrics publisher keys from Serviceability, replacing the need for static `--parent-dzd` CLI flags. Static parents from CLI are merged with onchain parents, with onchain taking precedence for duplicate keys. - Optimize inbound probe-measured RTT accuracy: pre-sign both TWAMP probes before network I/O so probe 1 fires immediately after reply 0 with no signing delay, measure Tx-to-Rx interval (reply 0 Tx → probe 1 Rx) instead of Rx-to-Rx to exclude processing overhead on both sides, use kernel `SO_TIMESTAMPNS` receive timestamps on the reflector, and add a 15ms busy-poll window on the sender to avoid scheduler wakeup latency - Optimize outbound probe RTT accuracy: send a staggered warmup probe on a separate socket 2ms before the measurement probe to wake the reflector's thread, then take the min RTT of both +- Onchain Programs + - Serviceability: add `Permission` account with `CreatePermission`, `UpdatePermission`, `DeletePermission`, `SuspendPermission`, and `ResumePermission` instructions for managing per-keypair permission bitmasks onchain +- SDK + - Split `execute_transaction` into `execute_transaction` (no auth) and `execute_authorized_transaction` (injects Permission PDA) to avoid breaking processors that use `accounts.len()` for optional-account detection +- CLI + - Add `permission get`, `permission list`, and `permission set` commands with table and JSON output; `permission set` supports incremental `--add` / `--remove` flags and creates or updates the account as needed ## [v0.11.0](https://github.com/malbeclabs/doublezero/compare/client/v0.10.0...client/v0.11.0) - 2026-03-12 diff --git a/activator/src/process/user.rs b/activator/src/process/user.rs index 782d0b6496..53c678b3fc 100644 --- a/activator/src/process/user.rs +++ b/activator/src/process/user.rs @@ -1775,7 +1775,7 @@ mod tests { UserStatus::Deleting, |user_service, _, seq| { user_service - .expect_execute_transaction_quiet() + .expect_execute_authorized_transaction_quiet() .times(1) .in_sequence(seq) .with( @@ -1799,7 +1799,7 @@ mod tests { UserStatus::PendingBan, |user_service, _, seq| { user_service - .expect_execute_transaction_quiet() + .expect_execute_authorized_transaction_quiet() .times(1) .in_sequence(seq) .with( @@ -2853,7 +2853,7 @@ mod tests { // Stateless mode: use_onchain_deallocation=true client - .expect_execute_transaction_quiet() + .expect_execute_authorized_transaction_quiet() .times(1) .in_sequence(&mut seq) .with( @@ -2959,7 +2959,7 @@ mod tests { .returning(move |_| Ok(AccountData::User(user2.clone()))); client - .expect_execute_transaction_quiet() + .expect_execute_authorized_transaction_quiet() .times(1) .in_sequence(&mut seq) .with( diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs index 0f43be89fe..99d3202bc8 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs @@ -1,7 +1,11 @@ use crate::{ + authorize::authorize, error::DoubleZeroError, serializer::try_acc_close, - state::{accesspass::AccessPass, accounttype::AccountType, globalstate::GlobalState}, + state::{ + accesspass::AccessPass, accounttype::AccountType, globalstate::GlobalState, + permission::permission_flags, + }, }; use borsh::BorshSerialize; use borsh_incremental::BorshDeserializeIncremental; @@ -70,13 +74,15 @@ pub fn process_close_access_pass( "Invalid System Program Account Owner" ); - // Parse the global state account & check if the payer is in the allowlist + // Parse the global state account & check authorization let globalstate = GlobalState::try_from(globalstate_account)?; - if !globalstate.foundation_allowlist.contains(payer_account.key) - && globalstate.reservation_authority_pk != *payer_account.key - { - return Err(DoubleZeroError::NotAllowed.into()); - } + authorize( + program_id, + accounts_iter, + payer_account.key, + &globalstate, + permission_flags::ACCESS_PASS_ADMIN, + )?; if let Ok(data) = accesspass_account.try_borrow_data() { let account_type: AccountType = data[0].into(); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs index 7c63e17d88..e04756167d 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs @@ -1,4 +1,5 @@ use crate::{ + authorize::authorize, error::DoubleZeroError, pda::*, seeds::{SEED_ACCESS_PASS, SEED_PREFIX}, @@ -7,6 +8,7 @@ use crate::{ accesspass::{AccessPass, AccessPassStatus, AccessPassType, ALLOW_MULTIPLE_IP, IS_DYNAMIC}, accounttype::AccountType, globalstate::GlobalState, + permission::permission_flags, tenant::Tenant, }, }; @@ -107,21 +109,15 @@ pub fn process_set_access_pass( "Invalid System Program Account Owner" ); - // Parse the global state account & check if the payer is in the allowlist + // Parse the global state account & check authorization let globalstate = GlobalState::try_from(globalstate_account)?; - if globalstate.sentinel_authority_pk != *payer_account.key - && globalstate.reservation_authority_pk != *payer_account.key - && !globalstate.foundation_allowlist.contains(payer_account.key) - { - msg!( - "sentinel_authority_pk: {} reservation_authority_pk: {} payer: {} foundation_allowlist: {:?}", - globalstate.sentinel_authority_pk, - globalstate.reservation_authority_pk, - payer_account.key, - globalstate.foundation_allowlist - ); - return Err(DoubleZeroError::NotAllowed.into()); - } + authorize( + program_id, + accounts_iter, + payer_account.key, + &globalstate, + permission_flags::ACCESS_PASS_ADMIN, + )?; if let AccessPassType::SolanaValidator(node_id) = value.accesspass_type { if node_id == Pubkey::default() { diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/ban.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/ban.rs index fb8d8a89bf..f634dc1788 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/ban.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/ban.rs @@ -1,7 +1,8 @@ use crate::{ + authorize::authorize, error::DoubleZeroError, serializer::try_acc_write, - state::{globalstate::GlobalState, user::*}, + state::{globalstate::GlobalState, permission::permission_flags, user::*}, }; use borsh::BorshSerialize; use borsh_incremental::BorshDeserializeIncremental; @@ -57,9 +58,13 @@ pub fn process_ban_user( assert!(user_account.is_writable, "PDA Account is not writable"); let globalstate = GlobalState::try_from(globalstate_account)?; - if globalstate.activator_authority_pk != *payer_account.key { - return Err(DoubleZeroError::NotAllowed.into()); - } + authorize( + program_id, + accounts_iter, + payer_account.key, + &globalstate, + permission_flags::USER_ADMIN, + )?; let mut user: User = User::try_from(user_account)?; if user.status != UserStatus::PendingBan { diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/closeaccount.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/closeaccount.rs index cf9a2dec39..7b685831cc 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/closeaccount.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/closeaccount.rs @@ -1,10 +1,13 @@ use crate::{ - error::DoubleZeroError, + authorize::authorize, pda::get_resource_extension_pda, processors::resource::{deallocate_id, deallocate_ip}, resource::ResourceType, serializer::{try_acc_close, try_acc_write}, - state::{device::Device, globalstate::GlobalState, tenant::Tenant, user::*}, + state::{ + device::Device, globalstate::GlobalState, permission::permission_flags, tenant::Tenant, + user::*, + }, }; use borsh::BorshSerialize; use borsh_incremental::BorshDeserializeIncremental; @@ -130,12 +133,13 @@ pub fn process_closeaccount_user( let globalstate = GlobalState::try_from(globalstate_account)?; - // Authorization: allow activator_authority_pk OR foundation_allowlist (matching ActivateUser) - let is_activator = globalstate.activator_authority_pk == *payer_account.key; - let is_foundation = globalstate.foundation_allowlist.contains(payer_account.key); - if !is_activator && !is_foundation { - return Err(DoubleZeroError::NotAllowed.into()); - } + authorize( + program_id, + accounts_iter, + payer_account.key, + &globalstate, + permission_flags::USER_ADMIN, + )?; let user = User::try_from(user_account)?; diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs index 5407e1c737..15df6ba357 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs @@ -1,4 +1,5 @@ use crate::{ + authorize::authorize, error::DoubleZeroError, pda::get_accesspass_pda, serializer::{try_acc_close, try_acc_write}, @@ -6,6 +7,7 @@ use crate::{ accesspass::{AccessPass, AccessPassStatus}, device::Device, globalstate::GlobalState, + permission::permission_flags, tenant::Tenant, user::*, }, @@ -143,10 +145,15 @@ pub fn process_delete_user( let user: User = User::try_from(user_account)?; let globalstate = GlobalState::try_from(globalstate_account)?; - if !globalstate.foundation_allowlist.contains(payer_account.key) - && user.owner != *payer_account.key - { - return Err(DoubleZeroError::NotAllowed.into()); + // The user owner can always delete their own account without a Permission account. + if user.owner != *payer_account.key { + authorize( + program_id, + accounts_iter, + payer_account.key, + &globalstate, + permission_flags::USER_ADMIN, + )?; } let (accesspass_pda, _) = get_accesspass_pda(program_id, &user.client_ip, &user.owner); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/requestban.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/requestban.rs index 03404a54b6..a044c52426 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/requestban.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/requestban.rs @@ -1,7 +1,8 @@ use crate::{ + authorize::authorize, error::DoubleZeroError, serializer::try_acc_write, - state::{globalstate::GlobalState, user::*}, + state::{globalstate::GlobalState, permission::permission_flags, user::*}, }; use borsh::BorshSerialize; use borsh_incremental::BorshDeserializeIncremental; @@ -104,9 +105,13 @@ pub fn process_request_ban_user( assert!(user_account.is_writable, "PDA Account is not writable"); let globalstate = GlobalState::try_from(globalstate_account)?; - if !globalstate.foundation_allowlist.contains(payer_account.key) { - return Err(DoubleZeroError::NotAllowed.into()); - } + authorize( + program_id, + accounts_iter, + payer_account.key, + &globalstate, + permission_flags::USER_ADMIN, + )?; let mut user: User = User::try_from(user_account)?; if !can_request_ban(user.status) { diff --git a/smartcontract/sdk/rs/src/client.rs b/smartcontract/sdk/rs/src/client.rs index ff243aaa09..10899abd47 100644 --- a/smartcontract/sdk/rs/src/client.rs +++ b/smartcontract/sdk/rs/src/client.rs @@ -6,7 +6,8 @@ use std::time::Duration; use crate::config::default_program_id; use doublezero_serviceability::{ - error::DoubleZeroError, instructions::*, state::accounttype::AccountType, + error::DoubleZeroError, instructions::*, pda::get_permission_pda, + state::accounttype::AccountType, }; use eyre::{bail, eyre, OptionExt}; use log::debug; @@ -374,6 +375,66 @@ impl DZClient { Ok(errors) } + + fn build_and_send( + &self, + instruction: DoubleZeroInstruction, + accounts: Vec, + with_permission: bool, + ) -> eyre::Result { + let payer = self + .payer + .as_ref() + .ok_or_eyre("No default signer found, run \"doublezero keygen\" to create a new one")?; + let data = instruction.pack(); + + let mut trailing = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(program::id(), false), + ]; + if with_permission { + let (permission_pda, _) = get_permission_pda(&self.program_id, &payer.pubkey()); + if self.client.get_account(&permission_pda).is_ok() { + trailing.push(AccountMeta::new_readonly(permission_pda, false)); + } + } + + let mut transaction = Transaction::new_with_payer( + &[Instruction::new_with_bytes( + self.program_id, + &data, + [accounts, trailing].concat(), + )], + Some(&payer.pubkey()), + ); + + let blockhash = self.client.get_latest_blockhash().map_err(|e| eyre!(e))?; + transaction.sign(&[&payer], blockhash); + + debug!("Simulating transaction: {transaction:?}"); + + let result = self.client.simulate_transaction(&transaction)?; + if result.value.err.is_some() { + eprintln!("Program Logs:"); + if let Some(logs) = result.value.logs { + for log in logs { + eprintln!("{log}"); + } + } + } + + if let Some(TransactionError::InstructionError(_index, InstructionError::Custom(number))) = + result.value.err + { + return Err(eyre!(DoubleZeroError::from(number))); + } else if let Some(err) = result.value.err { + return Err(eyre!(err)); + } + + self.client + .send_and_confirm_transaction(&transaction) + .map_err(|e| eyre!(e)) + } } impl DoubleZeroClient for DZClient { @@ -450,7 +511,7 @@ impl DoubleZeroClient for DZClient { instruction: DoubleZeroInstruction, accounts: Vec, ) -> eyre::Result { - self.execute_transaction_inner(instruction, accounts, false) + self.build_and_send(instruction, accounts, false) } fn execute_transaction_quiet( @@ -461,6 +522,29 @@ impl DoubleZeroClient for DZClient { self.execute_transaction_inner(instruction, accounts, true) } + fn execute_authorized_transaction( + &self, + instruction: DoubleZeroInstruction, + accounts: Vec, + ) -> eyre::Result { + self.build_and_send(instruction, accounts, true) + } + + fn execute_authorized_transaction_quiet( + &self, + instruction: DoubleZeroInstruction, + accounts: Vec, + ) -> eyre::Result { + let mut accounts = accounts; + if let Some(payer) = self.payer.as_ref() { + let (permission_pda, _) = get_permission_pda(&self.program_id, &payer.pubkey()); + if self.client.get_account(&permission_pda).is_ok() { + accounts.push(AccountMeta::new_readonly(permission_pda, false)); + } + } + self.execute_transaction_inner(instruction, accounts, true) + } + fn gets(&self, account_type: AccountType) -> eyre::Result> { let account_type = account_type as u8; let filters = vec![RpcFilterType::Memcmp(Memcmp::new( diff --git a/smartcontract/sdk/rs/src/commands/accesspass/close.rs b/smartcontract/sdk/rs/src/commands/accesspass/close.rs index 364676a6c4..15cc77b179 100644 --- a/smartcontract/sdk/rs/src/commands/accesspass/close.rs +++ b/smartcontract/sdk/rs/src/commands/accesspass/close.rs @@ -15,7 +15,7 @@ impl CloseAccessPassCommand { .execute(client) .map_err(|_err| eyre::eyre!("Globalstate not initialized"))?; - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::CloseAccessPass(CloseAccessPassArgs {}), vec![ AccountMeta::new(self.pubkey, false), @@ -50,7 +50,7 @@ mod tests { let (pda_pubkey, _) = get_accesspass_pda(&client.get_program_id(), &client_ip, &payer); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CloseAccessPass( CloseAccessPassArgs {}, diff --git a/smartcontract/sdk/rs/src/commands/accesspass/set.rs b/smartcontract/sdk/rs/src/commands/accesspass/set.rs index 26c566fc0d..049c1f127d 100644 --- a/smartcontract/sdk/rs/src/commands/accesspass/set.rs +++ b/smartcontract/sdk/rs/src/commands/accesspass/set.rs @@ -65,7 +65,7 @@ impl SetAccessPassCommand { accounts.push(AccountMeta::new(self.tenant, false)); } - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs { accesspass_type: self.accesspass_type.clone(), client_ip: self.client_ip, @@ -127,7 +127,7 @@ mod tests { .returning(move |_| Ok(AccountData::AccessPass(accesspass.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs { accesspass_type: AccessPassType::Prepaid, diff --git a/smartcontract/sdk/rs/src/commands/permission/create.rs b/smartcontract/sdk/rs/src/commands/permission/create.rs index 3c8b78d50e..af5887d448 100644 --- a/smartcontract/sdk/rs/src/commands/permission/create.rs +++ b/smartcontract/sdk/rs/src/commands/permission/create.rs @@ -20,7 +20,7 @@ impl CreatePermissionCommand { let (permission_pda, _) = get_permission_pda(&client.get_program_id(), &self.user_payer); client - .execute_transaction( + .execute_authorized_transaction( DoubleZeroInstruction::CreatePermission(PermissionCreateArgs { user_payer: self.user_payer, permissions: self.permissions, @@ -58,7 +58,7 @@ mod tests { let (permission_pda, _) = get_permission_pda(&client.get_program_id(), &user_payer); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CreatePermission( PermissionCreateArgs { diff --git a/smartcontract/sdk/rs/src/commands/permission/delete.rs b/smartcontract/sdk/rs/src/commands/permission/delete.rs index 928481de0b..2aa709d5e2 100644 --- a/smartcontract/sdk/rs/src/commands/permission/delete.rs +++ b/smartcontract/sdk/rs/src/commands/permission/delete.rs @@ -14,7 +14,7 @@ impl DeletePermissionCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { let (globalstate_pubkey, _) = get_globalstate_pda(&client.get_program_id()); - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::DeletePermission(PermissionDeleteArgs {}), vec![ AccountMeta::new(self.permission_pda, false), @@ -47,7 +47,7 @@ mod tests { let (permission_pda, _) = get_permission_pda(&client.get_program_id(), &user_payer); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::DeletePermission( PermissionDeleteArgs {}, diff --git a/smartcontract/sdk/rs/src/commands/permission/resume.rs b/smartcontract/sdk/rs/src/commands/permission/resume.rs index e855259455..38e3dd7fd4 100644 --- a/smartcontract/sdk/rs/src/commands/permission/resume.rs +++ b/smartcontract/sdk/rs/src/commands/permission/resume.rs @@ -14,7 +14,7 @@ impl ResumePermissionCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { let (globalstate_pubkey, _) = get_globalstate_pda(&client.get_program_id()); - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::ResumePermission(PermissionResumeArgs {}), vec![ AccountMeta::new(self.permission_pda, false), @@ -47,7 +47,7 @@ mod tests { let (permission_pda, _) = get_permission_pda(&client.get_program_id(), &user_payer); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::ResumePermission( PermissionResumeArgs {}, diff --git a/smartcontract/sdk/rs/src/commands/permission/suspend.rs b/smartcontract/sdk/rs/src/commands/permission/suspend.rs index 814c8930c1..36d8ff8e11 100644 --- a/smartcontract/sdk/rs/src/commands/permission/suspend.rs +++ b/smartcontract/sdk/rs/src/commands/permission/suspend.rs @@ -14,7 +14,7 @@ impl SuspendPermissionCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { let (globalstate_pubkey, _) = get_globalstate_pda(&client.get_program_id()); - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::SuspendPermission(PermissionSuspendArgs {}), vec![ AccountMeta::new(self.permission_pda, false), @@ -47,7 +47,7 @@ mod tests { let (permission_pda, _) = get_permission_pda(&client.get_program_id(), &user_payer); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::SuspendPermission( PermissionSuspendArgs {}, diff --git a/smartcontract/sdk/rs/src/commands/permission/update.rs b/smartcontract/sdk/rs/src/commands/permission/update.rs index 5f492c74a7..db1dbe8ca9 100644 --- a/smartcontract/sdk/rs/src/commands/permission/update.rs +++ b/smartcontract/sdk/rs/src/commands/permission/update.rs @@ -16,7 +16,7 @@ impl UpdatePermissionCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { let (globalstate_pubkey, _) = get_globalstate_pda(&client.get_program_id()); - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::UpdatePermission(PermissionUpdateArgs { add: self.add, remove: self.remove, @@ -54,7 +54,7 @@ mod tests { let (permission_pda, _) = get_permission_pda(&client.get_program_id(), &user_payer); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::UpdatePermission( PermissionUpdateArgs { add, remove }, diff --git a/smartcontract/sdk/rs/src/commands/tenant/delete.rs b/smartcontract/sdk/rs/src/commands/tenant/delete.rs index df8c29254e..b6afa95f4b 100644 --- a/smartcontract/sdk/rs/src/commands/tenant/delete.rs +++ b/smartcontract/sdk/rs/src/commands/tenant/delete.rs @@ -286,9 +286,9 @@ mod tests { .in_sequence(&mut seq) .returning(|_| Ok(HashMap::new())); - // 4. DeleteUserCommand internally: execute_transaction(DeleteUser) + // 4. DeleteUserCommand internally: execute_authorized_transaction(DeleteUser) client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::DeleteUser(UserDeleteArgs::default())), predicate::always(), @@ -313,9 +313,9 @@ mod tests { Ok(map) }); - // 6. SetAccessPassCommand: execute_transaction(SetAccessPass) to reset tenant + // 6. SetAccessPassCommand: execute_authorized_transaction(SetAccessPass) to reset tenant client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs { accesspass_type: AccessPassType::Prepaid, @@ -415,9 +415,9 @@ mod tests { Ok(map) }); - // 2. SetAccessPassCommand: execute_transaction(SetAccessPass) to reset tenant + // 2. SetAccessPassCommand: execute_authorized_transaction(SetAccessPass) to reset tenant client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs { accesspass_type: AccessPassType::Prepaid, diff --git a/smartcontract/sdk/rs/src/commands/user/ban.rs b/smartcontract/sdk/rs/src/commands/user/ban.rs index 0533be6521..93cd5101ce 100644 --- a/smartcontract/sdk/rs/src/commands/user/ban.rs +++ b/smartcontract/sdk/rs/src/commands/user/ban.rs @@ -24,16 +24,21 @@ impl BanUserCommand { .execute(client) .map_err(|_err| eyre::eyre!("Globalstate not initialized"))?; - let instruction = DoubleZeroInstruction::BanUser(UserBanArgs {}); let accounts = vec![ AccountMeta::new(self.pubkey, false), AccountMeta::new(globalstate_pubkey, false), ]; if quiet { - client.execute_transaction_quiet(instruction, accounts) + client.execute_authorized_transaction_quiet( + DoubleZeroInstruction::BanUser(UserBanArgs {}), + accounts, + ) } else { - client.execute_transaction(instruction, accounts) + client.execute_authorized_transaction( + DoubleZeroInstruction::BanUser(UserBanArgs {}), + accounts, + ) } } } diff --git a/smartcontract/sdk/rs/src/commands/user/closeaccount.rs b/smartcontract/sdk/rs/src/commands/user/closeaccount.rs index 998a7a20f0..21ab072a44 100644 --- a/smartcontract/sdk/rs/src/commands/user/closeaccount.rs +++ b/smartcontract/sdk/rs/src/commands/user/closeaccount.rs @@ -119,9 +119,9 @@ impl CloseAccountUserCommand { }); if quiet { - client.execute_transaction_quiet(instruction, accounts) + client.execute_authorized_transaction_quiet(instruction, accounts) } else { - client.execute_transaction(instruction, accounts) + client.execute_authorized_transaction(instruction, accounts) } } } @@ -185,7 +185,7 @@ mod tests { .returning(move |_| Ok(AccountData::User(user.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CloseAccountUser( UserCloseAccountArgs { @@ -289,7 +289,7 @@ mod tests { .returning(move |_| Ok(AccountData::GlobalConfig(globalconfig.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CloseAccountUser( UserCloseAccountArgs { @@ -354,7 +354,7 @@ mod tests { .returning(move |_| Ok(AccountData::User(user.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CloseAccountUser( UserCloseAccountArgs { @@ -457,7 +457,7 @@ mod tests { .returning(move |_| Ok(AccountData::GlobalConfig(globalconfig.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CloseAccountUser( UserCloseAccountArgs { diff --git a/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs b/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs index 438728f0d1..4b6a373412 100644 --- a/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs +++ b/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs @@ -117,7 +117,7 @@ impl CreateSubscribeUserCommand { }; client - .execute_transaction( + .execute_authorized_transaction( DoubleZeroInstruction::CreateSubscribeUser(UserCreateSubscribeArgs { user_type: self.user_type, cyoa_type: self.cyoa_type, @@ -205,7 +205,7 @@ mod tests { .returning(move |_| Ok(AccountData::AccessPass(accesspass.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CreateSubscribeUser( UserCreateSubscribeArgs { @@ -334,7 +334,7 @@ mod tests { get_resource_extension_pda(&program_id, ResourceType::DzPrefixBlock(device_pk, 0)); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::CreateSubscribeUser( UserCreateSubscribeArgs { diff --git a/smartcontract/sdk/rs/src/commands/user/delete.rs b/smartcontract/sdk/rs/src/commands/user/delete.rs index bbec473b1d..25eab0b892 100644 --- a/smartcontract/sdk/rs/src/commands/user/delete.rs +++ b/smartcontract/sdk/rs/src/commands/user/delete.rs @@ -168,7 +168,7 @@ impl DeleteUserCommand { (0u8, 0u8) }; - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::DeleteUser(UserDeleteArgs { dz_prefix_count, multicast_publisher_count, @@ -394,7 +394,7 @@ mod tests { // Execute transaction for DeleteUser client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::DeleteUser(UserDeleteArgs::default())), predicate::eq(vec![ @@ -595,7 +595,7 @@ mod tests { // DeleteUser transaction client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::DeleteUser(UserDeleteArgs::default())), predicate::eq(vec![ @@ -728,7 +728,7 @@ mod tests { get_resource_extension_pda(&program_id, ResourceType::DzPrefixBlock(device_pk, 0)); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::DeleteUser(UserDeleteArgs { dz_prefix_count: 1, diff --git a/smartcontract/sdk/rs/src/commands/user/requestban.rs b/smartcontract/sdk/rs/src/commands/user/requestban.rs index fa94559fd3..f9d23c9a9e 100644 --- a/smartcontract/sdk/rs/src/commands/user/requestban.rs +++ b/smartcontract/sdk/rs/src/commands/user/requestban.rs @@ -140,7 +140,7 @@ impl RequestBanUserCommand { (0u8, 0u8) }; - client.execute_transaction( + client.execute_authorized_transaction( DoubleZeroInstruction::RequestBanUser(UserRequestBanArgs { dz_prefix_count, multicast_publisher_count, @@ -263,7 +263,7 @@ mod tests { get_resource_extension_pda(&program_id, ResourceType::DzPrefixBlock(device_pk, 0)); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::RequestBanUser(UserRequestBanArgs { dz_prefix_count: 1, @@ -324,7 +324,7 @@ mod tests { .returning(move |_| Ok(AccountData::User(user.clone()))); client - .expect_execute_transaction() + .expect_execute_authorized_transaction() .with( predicate::eq(DoubleZeroInstruction::RequestBanUser( UserRequestBanArgs::default(), diff --git a/smartcontract/sdk/rs/src/doublezeroclient.rs b/smartcontract/sdk/rs/src/doublezeroclient.rs index 61e63007d1..8724e61428 100644 --- a/smartcontract/sdk/rs/src/doublezeroclient.rs +++ b/smartcontract/sdk/rs/src/doublezeroclient.rs @@ -50,6 +50,23 @@ pub trait DoubleZeroClient { accounts: Vec, ) -> eyre::Result; + /// Like `execute_transaction` but appends the payer's Permission PDA + /// (read-only) when it exists on-chain, so `authorize()` can find it. + /// Use this for instructions whose processor calls `authorize()`. + fn execute_authorized_transaction( + &self, + instruction: DoubleZeroInstruction, + accounts: Vec, + ) -> eyre::Result; + + /// Like `execute_authorized_transaction`, but suppresses program log output on simulation failure. + /// Use this for authorized transactions where simulation failures are expected (e.g., race conditions). + fn execute_authorized_transaction_quiet( + &self, + instruction: DoubleZeroInstruction, + accounts: Vec, + ) -> eyre::Result; + fn get_transactions(&self, pubkey: Pubkey) -> eyre::Result>; }