Skip to content
Merged
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,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
- Add `execute_authorized_transaction` (and its `_quiet` variant) alongside `execute_transaction`. The authorized variants append the payer's Permission PDA (read-only) as the trailing account when it exists on-chain, so `authorize()` can find it. All variants share the same builder, so the protocol-max compute-budget/heap-frame requests, preflight, and error-reporting behavior are identical to `execute_transaction`; the only difference is the optional trailing Permission account. The Permission PDA lookup is retried on transient RPC errors and memoized per client.
- 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

Expand Down
251 changes: 221 additions & 30 deletions smartcontract/programs/doublezero-serviceability/src/authorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ use solana_program::{
/// SENTINEL → sentinel_authority_pk
/// HEALTH_ORACLE → health_oracle_pk
/// FEED_AUTHORITY → feed_authority_pk
/// USER_ADMIN → foundation_allowlist OR activator_authority_pk
/// USER_ADMIN → foundation_allowlist
/// ACCESS_PASS_ADMIN → foundation_allowlist OR sentinel_authority_pk OR feed_authority_pk
/// NETWORK_ADMIN → foundation_allowlist OR activator_authority_pk
/// NETWORK_ADMIN → foundation_allowlist
/// TENANT_ADMIN → foundation_allowlist OR sentinel_authority_pk
/// MULTICAST_ADMIN → foundation_allowlist OR activator_authority_pk OR sentinel_authority_pk
/// PERMISSION_ADMIN → foundation_allowlist (also allowed even when RequirePermissionAccounts is set)
/// MULTICAST_ADMIN → foundation_allowlist OR sentinel_authority_pk
/// PERMISSION_ADMIN → foundation_allowlist (always allowed for foundation, even under
/// RequirePermissionAccounts or when the payer's own Permission account
/// is missing/suspended/under-privileged — see foundation_permission_recovery)
/// INFRA_ADMIN → foundation_allowlist
/// GLOBALSTATE_ADMIN → foundation_allowlist
/// CONTRIBUTOR_ADMIN → foundation_allowlist
Expand All @@ -57,17 +59,28 @@ where
if permission_account.key != &expected_pda {
return Err(ProgramError::InvalidArgument);
}
if permission_account.data_is_empty() {
return Err(DoubleZeroError::NotAllowed.into());
}
// Verify program ownership immediately after the PDA-address check, before
// inspecting the account's contents.
if permission_account.owner != program_id {
return Err(ProgramError::InvalidAccountData);
}
let permission = Permission::try_from(permission_account)?;
if permission.status != PermissionStatus::Activated {
return Err(DoubleZeroError::NotAllowed.into());
}
if permission.permissions & any_of_flags == 0 {
// Whether the supplied Permission account itself grants one of the flags.
let granted = !permission_account.data_is_empty()
&& Permission::try_from(permission_account)
.map(|p| {
p.status == PermissionStatus::Activated && p.permissions & any_of_flags != 0
})
.unwrap_or(false);
if !granted {
// The SDK auto-appends the payer's Permission PDA whenever it exists
// on-chain. Without the recovery below, a foundation member whose own
// Permission account is suspended, under-privileged, or uninitialized
// would be routed through this branch and denied the very PERMISSION_ADMIN
// instruction needed to repair it — re-introducing the lockout the
// None-branch fallback exists to prevent.
if foundation_permission_recovery(globalstate, payer_key, any_of_flags) {
return Ok(());
}
return Err(DoubleZeroError::NotAllowed.into());
}
}
Expand All @@ -79,9 +92,7 @@ where
) {
// Even in strict mode, foundation members can manage permissions to
// prevent being locked out of the permission system.
if any_of_flags & permission_flags::PERMISSION_ADMIN != 0
&& globalstate.foundation_allowlist.contains(payer_key)
{
if foundation_permission_recovery(globalstate, payer_key, any_of_flags) {
return Ok(());
}
return Err(DoubleZeroError::NotAllowed.into());
Expand All @@ -94,6 +105,19 @@ where
Ok(())
}

/// Foundation lockout recovery: a foundation member may always exercise
/// `PERMISSION_ADMIN`, even in strict mode or when their own Permission account is
/// missing, suspended, or lacks the flag. This guarantees the permission system can
/// never lock foundation out of managing permissions.
fn foundation_permission_recovery(
globalstate: &GlobalState,
payer_key: &Pubkey,
any_of_flags: u128,
) -> bool {
any_of_flags & permission_flags::PERMISSION_ADMIN != 0
&& globalstate.foundation_allowlist.contains(payer_key)
}

/// Returns true if `payer` satisfies at least one of the requested flags using legacy
/// GlobalState fields.
fn check_legacy_any(payer: &Pubkey, globalstate: &GlobalState, any_of: u128) -> bool {
Expand All @@ -117,25 +141,28 @@ fn check_legacy_any(payer: &Pubkey, globalstate: &GlobalState, any_of: u128) ->
if any_of & permission_flags::FEED_AUTHORITY != 0 && globalstate.feed_authority_pk == *payer {
return true;
}
// USER_ADMIN in legacy = foundation or activator (historical user management authorities).
// USER_ADMIN in legacy = foundation only. (The activator authority has been
Comment thread
vihu marked this conversation as resolved.
// retired from the system, so it no longer grants user-management rights.)
if any_of & permission_flags::USER_ADMIN != 0
&& (globalstate.foundation_allowlist.contains(payer)
|| globalstate.activator_authority_pk == *payer)
&& globalstate.foundation_allowlist.contains(payer)
{
return true;
}
// ACCESS_PASS_ADMIN in legacy = foundation, sentinel, or feed authority.
// ACCESS_PASS_ADMIN in legacy = foundation, sentinel, or feed authority. This
// mirrors the historical accesspass/set authority and is applied uniformly to
// all ACCESS_PASS_ADMIN instructions (so accesspass/close, previously
// foundation+feed only, now also accepts the sentinel authority).
if any_of & permission_flags::ACCESS_PASS_ADMIN != 0
&& (globalstate.foundation_allowlist.contains(payer)
|| globalstate.sentinel_authority_pk == *payer
|| globalstate.feed_authority_pk == *payer)
{
return true;
}
// NETWORK_ADMIN in legacy = foundation or activator.
// NETWORK_ADMIN in legacy = foundation only. (The activator authority has
// been retired from the system.)
if any_of & permission_flags::NETWORK_ADMIN != 0
&& (globalstate.foundation_allowlist.contains(payer)
|| globalstate.activator_authority_pk == *payer)
&& globalstate.foundation_allowlist.contains(payer)
{
return true;
}
Expand All @@ -146,10 +173,10 @@ fn check_legacy_any(payer: &Pubkey, globalstate: &GlobalState, any_of: u128) ->
{
return true;
}
// MULTICAST_ADMIN in legacy = foundation, activator, or sentinel.
// MULTICAST_ADMIN in legacy = foundation or sentinel. (The activator authority
// has been retired from the system.)
if any_of & permission_flags::MULTICAST_ADMIN != 0
&& (globalstate.foundation_allowlist.contains(payer)
|| globalstate.activator_authority_pk == *payer
|| globalstate.sentinel_authority_pk == *payer)
{
return true;
Expand Down Expand Up @@ -347,11 +374,12 @@ mod tests {
}

#[test]
fn test_legacy_user_admin_via_activator() {
fn test_legacy_user_admin_activator_denied() {
// The activator authority has been retired and no longer grants USER_ADMIN.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let gs = gs_with_activator(&payer);
assert!(authorize_legacy(&program_id, &payer, &gs, permission_flags::USER_ADMIN).is_ok());
assert!(authorize_legacy(&program_id, &payer, &gs, permission_flags::USER_ADMIN).is_err());
}

#[test]
Expand Down Expand Up @@ -429,12 +457,13 @@ mod tests {
}

#[test]
fn test_legacy_network_admin_via_activator() {
fn test_legacy_network_admin_activator_denied() {
// The activator authority has been retired and no longer grants NETWORK_ADMIN.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let gs = gs_with_activator(&payer);
assert!(
authorize_legacy(&program_id, &payer, &gs, permission_flags::NETWORK_ADMIN).is_ok()
authorize_legacy(&program_id, &payer, &gs, permission_flags::NETWORK_ADMIN).is_err()
);
}

Expand Down Expand Up @@ -485,12 +514,13 @@ mod tests {
}

#[test]
fn test_legacy_multicast_admin_via_activator() {
fn test_legacy_multicast_admin_activator_denied() {
// The activator authority has been retired and no longer grants MULTICAST_ADMIN.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let gs = gs_with_activator(&payer);
assert!(
authorize_legacy(&program_id, &payer, &gs, permission_flags::MULTICAST_ADMIN).is_ok()
authorize_legacy(&program_id, &payer, &gs, permission_flags::MULTICAST_ADMIN).is_err()
);
}

Expand Down Expand Up @@ -996,6 +1026,167 @@ mod tests {
.is_err());
}

// ── Foundation lockout recovery (Permission account present but unusable) ──

#[test]
fn test_permission_account_foundation_recovery_when_suspended() {
// Foundation member whose own Permission account is suspended must still be
// able to exercise PERMISSION_ADMIN to repair/resume it, even though the SDK
// auto-appends the (unusable) Permission account.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let (pda, _, mut data) = make_permission_data(
&program_id,
&payer,
PermissionStatus::Suspended,
permission_flags::PERMISSION_ADMIN,
);

let mut lamports = 100_000u64;
let account = AccountInfo::new(
&pda,
false,
false,
&mut lamports,
&mut data,
&program_id,
false,
Epoch::default(),
);
let accounts = [account];
let mut iter = accounts.iter();
let gs = gs_with_foundation(&payer);

assert!(authorize(
&program_id,
&mut iter,
&payer,
&gs,
permission_flags::PERMISSION_ADMIN
)
.is_ok());
}

#[test]
fn test_permission_account_foundation_recovery_when_missing_flag() {
// Foundation member whose Permission account lacks PERMISSION_ADMIN can still
// manage permissions via the recovery fallback.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let (pda, _, mut data) = make_permission_data(
&program_id,
&payer,
PermissionStatus::Activated,
permission_flags::QA, // no PERMISSION_ADMIN
);

let mut lamports = 100_000u64;
let account = AccountInfo::new(
&pda,
false,
false,
&mut lamports,
&mut data,
&program_id,
false,
Epoch::default(),
);
let accounts = [account];
let mut iter = accounts.iter();
let gs = gs_with_foundation(&payer);

assert!(authorize(
&program_id,
&mut iter,
&payer,
&gs,
permission_flags::PERMISSION_ADMIN
)
.is_ok());
}

#[test]
fn test_permission_account_non_foundation_suspended_still_denied() {
// The recovery is foundation-only: a non-foundation payer with a suspended
// Permission account is still denied.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let (pda, _, mut data) = make_permission_data(
&program_id,
&payer,
PermissionStatus::Suspended,
permission_flags::PERMISSION_ADMIN,
);

let mut lamports = 100_000u64;
let account = AccountInfo::new(
&pda,
false,
false,
&mut lamports,
&mut data,
&program_id,
false,
Epoch::default(),
);
let accounts = [account];
let mut iter = accounts.iter();
let gs = GlobalState::default(); // payer not in foundation

assert_eq!(
authorize(
&program_id,
&mut iter,
&payer,
&gs,
permission_flags::PERMISSION_ADMIN
)
.unwrap_err(),
DoubleZeroError::NotAllowed.into()
);
}

#[test]
fn test_permission_account_foundation_recovery_only_for_permission_admin() {
// Recovery applies only to PERMISSION_ADMIN. A foundation member with a
// suspended Permission account cannot use it to satisfy USER_ADMIN.
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let (pda, _, mut data) = make_permission_data(
&program_id,
&payer,
PermissionStatus::Suspended,
permission_flags::USER_ADMIN,
);

let mut lamports = 100_000u64;
let account = AccountInfo::new(
&pda,
false,
false,
&mut lamports,
&mut data,
&program_id,
false,
Epoch::default(),
);
let accounts = [account];
let mut iter = accounts.iter();
let gs = gs_with_foundation(&payer);

assert_eq!(
authorize(
&program_id,
&mut iter,
&payer,
&gs,
permission_flags::USER_ADMIN
)
.unwrap_err(),
DoubleZeroError::NotAllowed.into()
);
}

// ── New path overrides feature flag enforcement ───────────────────────────

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.feed_authority_pk != *payer_account.key
{
return Err(DoubleZeroError::NotAllowed.into());
}
authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::ACCESS_PASS_ADMIN,
)?;
Comment thread
ben-dz marked this conversation as resolved.

if let Ok(data) = accesspass_account.try_borrow_data() {
let account_type: AccountType = data[0].into();
Expand Down
Loading
Loading