Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.

### Changes

- Serviceability
- Gate Device and device-interface instructions on `NETWORK_ADMIN` (and `HEALTH_ORACLE` for sethealth) or the contributor owner via `authorize()`; internal foundation-only sub-gates now also accept NETWORK_ADMIN holders. (#3980)
- Collector
- Harden ledger writes against a slow/degraded RPC endpoint: bound each RPC request (default 15s, `--ledger-rpc-timeout`), size the connection pool above the submitter concurrency (default 128, `--ledger-rpc-max-conns`), and deadline each submission attempt so it fails fast and retries with a fresh blockhash instead of sending an expired one and failing preflight with `BlockhashNotFound`. (#3973)
- E2E
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
authorize::authorize,
error::DoubleZeroError,
pda::get_device_pda,
processors::resource::create_resource,
Expand All @@ -7,7 +8,7 @@ use crate::{
serializer::{try_acc_create, try_acc_write},
state::{
accounttype::AccountType, contributor::Contributor, device::*, exchange::Exchange,
globalstate::GlobalState, location::Location,
globalstate::GlobalState, location::Location, permission::permission_flags,
},
};
use borsh::BorshSerialize;
Expand Down Expand Up @@ -127,8 +128,17 @@ pub fn process_create_device(

let mut contributor = Contributor::try_from(contributor_account)?;

// Authorization: the contributor owner, or NETWORK_ADMIN (Permission account) /
// foundation (legacy).
if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
&& authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::NETWORK_ADMIN,
)
.is_err()
{
return Err(DoubleZeroError::InvalidOwnerPubkey.into());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::{
authorize::authorize,
error::DoubleZeroError,
processors::validation::validate_program_account,
serializer::{try_acc_close, try_acc_write},
state::{
accounttype::AccountType, contributor::Contributor, device::*, exchange::Exchange,
globalstate::GlobalState, location::Location,
globalstate::GlobalState, location::Location, permission::permission_flags,
},
};
use borsh::BorshSerialize;
Expand Down Expand Up @@ -104,8 +105,17 @@ pub fn process_delete_device(

let mut contributor = Contributor::try_from(contributor_account)?;

// Authorization: the contributor owner, or NETWORK_ADMIN (Permission account) /
// foundation (legacy).
if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
&& authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::NETWORK_ADMIN,
)
.is_err()
{
return Err(DoubleZeroError::NotAllowed.into());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
authorize::authorize,
error::DoubleZeroError,
pda::get_resource_extension_pda,
processors::{
Expand All @@ -16,6 +17,7 @@ use crate::{
Interface, InterfaceCYOA, InterfaceDIA, InterfaceStatus, InterfaceType, LoopbackType,
RoutingMode, CURRENT_INTERFACE_SCHEMA_VERSION, CYOA_DIA_INTERFACE_MTU, INTERFACE_MTU,
},
permission::permission_flags,
topology::FlexAlgoNodeSegment,
},
};
Expand Down Expand Up @@ -136,9 +138,18 @@ pub fn process_create_device_interface(

let contributor = Contributor::try_from(contributor_account)?;

if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
{
// Authorization: the contributor owner, or NETWORK_ADMIN (Permission account) /
// foundation (legacy). Privileged callers also bypass the device-contributor
// binding checked below.
let is_privileged = authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::NETWORK_ADMIN,
)
.is_ok();
if contributor.owner != *payer_account.key && !is_privileged {
return Err(DoubleZeroError::InvalidOwnerPubkey.into());
}

Expand Down Expand Up @@ -184,9 +195,7 @@ pub fn process_create_device_interface(

// The supplied contributor must be the one the device belongs to,
// unless the payer is on the foundation allowlist.
if !globalstate.foundation_allowlist.contains(payer_account.key)
&& device.contributor_pk != *contributor_account.key
{
if !is_privileged && device.contributor_pk != *contributor_account.key {
return Err(DoubleZeroError::InvalidContributorPubkey.into());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
authorize::authorize,
error::DoubleZeroError,
pda::get_resource_extension_pda,
processors::{
Expand All @@ -13,6 +14,7 @@ use crate::{
device::*,
globalstate::GlobalState,
interface::{InterfaceStatus, InterfaceType, LoopbackType},
permission::permission_flags,
},
};
use borsh::BorshSerialize;
Expand Down Expand Up @@ -105,19 +107,26 @@ pub fn process_delete_device_interface(

let contributor = Contributor::try_from(contributor_account)?;

if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
{
// Authorization: the contributor owner, or NETWORK_ADMIN (Permission account) /
// foundation (legacy). Privileged callers also bypass the device-contributor
// binding below.
let is_privileged = authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::NETWORK_ADMIN,
)
.is_ok();
if contributor.owner != *payer_account.key && !is_privileged {
return Err(DoubleZeroError::NotAllowed.into());
}

let mut device: Device = Device::try_from(device_account)?;

// The supplied contributor must be the one the device belongs to,
// unless the payer is on the foundation allowlist.
if !globalstate.foundation_allowlist.contains(payer_account.key)
&& device.contributor_pk != *contributor_account.key
{
// The supplied contributor must be the one the device belongs to, unless the
// caller is privileged (foundation or NETWORK_ADMIN).
if !is_privileged && device.contributor_pk != *contributor_account.key {
return Err(DoubleZeroError::InvalidContributorPubkey.into());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
authorize::{authorize, split_trailing_permission},
error::{DoubleZeroError, Validate},
format_option,
helper::format_option_displayable,
Expand All @@ -18,6 +19,7 @@ use crate::{
InterfaceCYOA, InterfaceDIA, InterfaceStatus, InterfaceType, LoopbackType, RoutingMode,
CYOA_DIA_INTERFACE_MTU, INTERFACE_MTU,
},
permission::permission_flags,
topology::FlexAlgoNodeSegment,
},
};
Expand Down Expand Up @@ -110,21 +112,25 @@ pub fn process_update_device_interface(
// The presence of update_topologies forces seg_ext consumption; otherwise
// fall back to the legacy account-count heuristic so callers that set
// node_segment_idx without onchain allocation enabled still work.
let segment_routing_ids_ext = if value.update_topologies || accounts.len() > 5 {
Some(next_account_info(accounts_iter)?)
// Remaining tail: [segment_routing_ids?, topology_0..N?, payer, system, permission?].
// split_trailing_permission peels payer/system — and the optional payer Permission
// PDA the SDK appends when it exists — off the tail by PDA match, so it never
// inflates the optional-account detection. What's left (`mid`) is the optional
// SegmentRoutingIds account followed by the optional topology union.
let rest: Vec<&AccountInfo> = accounts_iter.collect();
let (payer_account, _system_program, mid, permission_account) =
split_trailing_permission(program_id, &rest)?;
let seg_ext_present = value.update_topologies || !mid.is_empty();
let segment_routing_ids_ext = if seg_ext_present {
mid.first().copied()
} else {
None
};

let mut topology_accounts = Vec::new();
if value.update_topologies {
for _ in 0..value.topology_count {
topology_accounts.push(next_account_info(accounts_iter)?);
}
}

let payer_account = next_account_info(accounts_iter)?;
let _system_program = next_account_info(accounts_iter)?;
let topology_accounts: Vec<&AccountInfo> = if seg_ext_present {
mid.get(1..).unwrap_or(&[]).to_vec()
} else {
Vec::new()
};

#[cfg(test)]
msg!("process_update_device_interface({:?})", value);
Expand Down Expand Up @@ -152,19 +158,26 @@ pub fn process_update_device_interface(

let contributor = Contributor::try_from(contributor_account)?;

if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
{
// Authorization: the contributor owner, or NETWORK_ADMIN (Permission account) /
// foundation (legacy). Privileged callers also bypass the device-contributor
// binding and the foundation-only gates below.
let is_privileged = authorize(
program_id,
&mut permission_account.into_iter(),
payer_account.key,
&globalstate,
permission_flags::NETWORK_ADMIN,
)
.is_ok();
if contributor.owner != *payer_account.key && !is_privileged {
return Err(DoubleZeroError::NotAllowed.into());
}

let mut device: Device = Device::try_from(device_account)?;

// The supplied contributor must be the one the device belongs to,
// unless the payer is on the foundation allowlist.
if !globalstate.foundation_allowlist.contains(payer_account.key)
&& device.contributor_pk != *contributor_account.key
{
// The supplied contributor must be the one the device belongs to, unless the
// caller is privileged (foundation or NETWORK_ADMIN).
if !is_privileged && device.contributor_pk != *contributor_account.key {
return Err(DoubleZeroError::InvalidContributorPubkey.into());
}

Expand Down Expand Up @@ -229,7 +242,7 @@ pub fn process_update_device_interface(
iface.ip_net = ip_net;
}
if let Some(node_segment_idx) = value.node_segment_idx {
if !globalstate.foundation_allowlist.contains(payer_account.key) {
if !is_privileged {
return Err(DoubleZeroError::NotAllowed.into());
}

Expand Down Expand Up @@ -263,9 +276,7 @@ pub fn process_update_device_interface(
// for removed topologies have their SR ID deallocated; new topologies get a
// freshly allocated SR ID.
if value.update_topologies {
if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
{
if contributor.owner != *payer_account.key && !is_privileged {
return Err(DoubleZeroError::NotAllowed.into());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{
authorize::authorize,
error::DoubleZeroError,
serializer::try_acc_write,
state::{
accounttype::AccountType, contributor::Contributor, device::*, globalstate::GlobalState,
permission::permission_flags,
},
};
use borsh::BorshSerialize;
Expand Down Expand Up @@ -74,8 +76,17 @@ pub fn process_resume_device(

let contributor = Contributor::try_from(contributor_account)?;

// Authorization: the contributor owner, or NETWORK_ADMIN (Permission account) /
// foundation (legacy).
if contributor.owner != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
&& authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::NETWORK_ADMIN,
)
.is_err()
{
return Err(DoubleZeroError::NotAllowed.into());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use core::fmt;

use crate::{
error::DoubleZeroError,
authorize::authorize,
serializer::try_acc_write,
state::{accounttype::AccountType, device::*, globalstate::GlobalState},
state::{
accounttype::AccountType, device::*, globalstate::GlobalState, permission::permission_flags,
},
};
use borsh::BorshSerialize;
use borsh_incremental::BorshDeserializeIncremental;
Expand Down Expand Up @@ -62,11 +64,16 @@ pub fn process_set_health_device(
let globalstate = GlobalState::try_from(globalstate_account)?;
assert_eq!(globalstate.account_type, AccountType::GlobalState);

if globalstate.health_oracle_pk != *payer_account.key
&& !globalstate.foundation_allowlist.contains(payer_account.key)
{
return Err(DoubleZeroError::NotAllowed.into());
}
// Authorization: HEALTH_ORACLE or foundation, via a Permission account or the
// legacy health_oracle_pk / foundation_allowlist (HEALTH_ORACLE covers the
// oracle key, NETWORK_ADMIN covers foundation).
authorize(
program_id,
accounts_iter,
payer_account.key,
&globalstate,
permission_flags::HEALTH_ORACLE | permission_flags::NETWORK_ADMIN,
)?;

let mut device: Device = Device::try_from(device_account)?;
device.device_health = value.health;
Expand Down
Loading
Loading