diff --git a/CHANGELOG.md b/CHANGELOG.md index 6febd1942..a54f61e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ### Breaking +- Updated the device update command to allow modifying a device’s location. + ### Changes - RFCs - RFC9 Link Draining @@ -82,6 +84,7 @@ All notable changes to this project will be documented in this file. - serviceability: prevent device interface name duplication - Update serviceability and telemetry program instruction args to use the `BorshDeserializeIncremental` derive macro incremental, backward-compatible, deserialization of structs. - Add explicit signer checks for payer accounts across various processors to improve security and ensure correct transaction authorization. + - Add the ability to update a Device’s location, managing the reference counters accordingly. - CLI - Removed `--bgp-community` option from `doublezero exchange create` since these values are now assigned automatically - Add `--next-bgp-community` option to `doublezero global-config set` so authorized users can control which bgp_community will be assigned next diff --git a/smartcontract/cli/src/device/update.rs b/smartcontract/cli/src/device/update.rs index 11a8908f8..9ac5ed08a 100644 --- a/smartcontract/cli/src/device/update.rs +++ b/smartcontract/cli/src/device/update.rs @@ -35,6 +35,9 @@ pub struct UpdateDeviceCliCommand { /// Contributor Pubkey (optional) #[arg(long, value_parser = validate_pubkey)] pub contributor: Option, + /// Location Pubkey (optional) + #[arg(long, value_parser = validate_pubkey)] + pub location: Option, /// Management VRF name (optional) #[arg(long)] pub mgmt_vrf: Option, @@ -113,6 +116,10 @@ impl UpdateDeviceCliCommand { dz_prefixes: self.dz_prefixes, metrics_publisher, contributor_pk: contributor, + location_pk: match &self.location { + Some(location) => Some(Pubkey::from_str(location)?), + None => None, + }, mgmt_vrf: self.mgmt_vrf, interfaces: None, max_users: self.max_users, @@ -258,6 +265,9 @@ mod tests { contributor_pk: Some(Pubkey::from_str_const( "HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx", )), + location_pk: Some(Pubkey::from_str_const( + "HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx", + )), mgmt_vrf: Some("default".to_string()), interfaces: None, max_users: Some(1025), @@ -275,6 +285,7 @@ mod tests { dz_prefixes: Some("1.2.3.4/32".parse().unwrap()), metrics_publisher: Some("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx".to_string()), contributor: Some("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx".to_string()), + location: Some("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx".to_string()), mgmt_vrf: Some("default".to_string()), max_users: Some(1025), users_count: Some(0), @@ -364,6 +375,7 @@ mod tests { public_ip: None, dz_prefixes: None, metrics_publisher: None, + location: None, contributor: None, mgmt_vrf: None, max_users: Some(255), @@ -453,6 +465,7 @@ mod tests { public_ip: Some([10, 20, 30, 40].into()), dz_prefixes: None, metrics_publisher: None, + location: None, contributor: None, mgmt_vrf: None, max_users: None, diff --git a/smartcontract/programs/doublezero-serviceability/src/error.rs b/smartcontract/programs/doublezero-serviceability/src/error.rs index 5e5d000fd..40ca0e739 100644 --- a/smartcontract/programs/doublezero-serviceability/src/error.rs +++ b/smartcontract/programs/doublezero-serviceability/src/error.rs @@ -121,6 +121,8 @@ pub enum DoubleZeroError { InvalidInterfaceType, // variant 57 #[error("Invalid Loopback Type")] InvalidLoopbackType, // variant 58 + #[error("Invalid Actual Location")] + InvalidActualLocation, // variant 59 } impl From for ProgramError { @@ -185,6 +187,7 @@ impl From for ProgramError { DoubleZeroError::InterfaceAlreadyExists => ProgramError::Custom(56), DoubleZeroError::InvalidInterfaceType => ProgramError::Custom(57), DoubleZeroError::InvalidLoopbackType => ProgramError::Custom(58), + DoubleZeroError::InvalidActualLocation => ProgramError::Custom(59), } } } @@ -250,6 +253,7 @@ impl From for DoubleZeroError { 56 => DoubleZeroError::InterfaceAlreadyExists, 57 => DoubleZeroError::InvalidInterfaceType, 58 => DoubleZeroError::InvalidLoopbackType, + 59 => DoubleZeroError::InvalidActualLocation, _ => DoubleZeroError::Custom(e), } } @@ -335,6 +339,7 @@ mod tests { InterfaceAlreadyExists, InvalidInterfaceType, InvalidLoopbackType, + InvalidActualLocation, ]; for err in variants { let pe: ProgramError = err.clone().into(); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/device/update.rs b/smartcontract/programs/doublezero-serviceability/src/processors/device/update.rs index 005e23db7..407e2c083 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/device/update.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/device/update.rs @@ -2,17 +2,16 @@ use crate::{ error::DoubleZeroError, globalstate::globalstate_get, helper::*, - state::{accounttype::AccountType, contributor::Contributor, device::*}, + state::{accounttype::AccountType, contributor::Contributor, device::*, location::Location}, }; use borsh::BorshSerialize; use borsh_incremental::BorshDeserializeIncremental; use core::fmt; use doublezero_program_common::{types::NetworkV4List, validate_account_code}; -#[cfg(test)] -use solana_program::msg; use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, + msg, pubkey::Pubkey, }; @@ -71,6 +70,17 @@ pub fn process_update_device( let device_account = next_account_info(accounts_iter)?; let contributor_account = next_account_info(accounts_iter)?; + // Update location accounts (old and new) + + let (location_old_account, location_new_account) = if accounts.len() == 7 { + ( + Some(next_account_info(accounts_iter)?), + Some(next_account_info(accounts_iter)?), + ) + } else { + (None, None) + }; + let globalstate_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; @@ -148,6 +158,43 @@ pub fn process_update_device( device.max_users = max_users; } + // Handle location update if both old and new location accounts are provided + if let (Some(location_old_account), Some(location_new_account)) = + (location_old_account, location_new_account) + { + if location_old_account.key != location_new_account.key { + let mut location_old = Location::try_from(location_old_account)?; + let mut location_new = Location::try_from(location_new_account)?; + if device.location_pk != *location_old_account.key { + msg!( + "Invalid location account. Device location_pk: {}, location_old_account: {}", + device.location_pk, + location_old_account.key + ); + return Err(DoubleZeroError::InvalidActualLocation.into()); + } + + location_old.reference_count = location_old.reference_count.saturating_sub(1); + location_new.reference_count = location_new.reference_count.saturating_add(1); + + // Set new location pk in device + device.location_pk = *location_new_account.key; + + account_write( + location_old_account, + &location_old, + payer_account, + system_program, + )?; + account_write( + location_new_account, + &location_new, + payer_account, + system_program, + )?; + } + } + account_write(device_account, &device, payer_account, system_program)?; #[cfg(test)] diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/link/update.rs b/smartcontract/programs/doublezero-serviceability/src/processors/link/update.rs index ba4c6ae90..9d91d3a72 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/link/update.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/link/update.rs @@ -2,17 +2,17 @@ use crate::{ error::DoubleZeroError, globalstate::globalstate_get, helper::*, - state::{contributor::Contributor, link::*}, + state::{contributor::Contributor, device::Device, link::*}, }; use borsh::BorshSerialize; use borsh_incremental::BorshDeserializeIncremental; use core::fmt; use doublezero_program_common::validate_account_code; -#[cfg(test)] -use solana_program::msg; + use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, + msg, pubkey::Pubkey, }; #[derive(BorshSerialize, BorshDeserializeIncremental, PartialEq, Clone, Default)] @@ -30,11 +30,35 @@ pub struct LinkUpdateArgs { impl fmt::Debug for LinkUpdateArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "code: {:?}, tunnel_type: {:?}, bandwidth: {:?}, mtu: {:?}, delay_ns: {:?}, jitter_ns: {:?}, delay_override_ns: {:?}", - self.code, self.tunnel_type, self.bandwidth, self.mtu, self.delay_ns, self.jitter_ns, self.delay_override_ns - ) + let mut parts = Vec::new(); + if let Some(ref code) = self.code { + parts.push(format!("code: {:?}", code)); + } + if let Some(ref contributor_pk) = self.contributor_pk { + parts.push(format!("contributor_pk: {:?}", contributor_pk)); + } + if let Some(ref tunnel_type) = self.tunnel_type { + parts.push(format!("tunnel_type: {:?}", tunnel_type)); + } + if let Some(bandwidth) = self.bandwidth { + parts.push(format!("bandwidth: {:?}", bandwidth)); + } + if let Some(mtu) = self.mtu { + parts.push(format!("mtu: {:?}", mtu)); + } + if let Some(delay_ns) = self.delay_ns { + parts.push(format!("delay_ns: {:?}", delay_ns)); + } + if let Some(jitter_ns) = self.jitter_ns { + parts.push(format!("jitter_ns: {:?}", jitter_ns)); + } + if let Some(ref status) = self.status { + parts.push(format!("status: {:?}", status)); + } + if let Some(delay_override_ns) = self.delay_override_ns { + parts.push(format!("delay_override_ns: {:?}", delay_override_ns)); + } + write!(f, "{}", parts.join(", ")) } } @@ -47,6 +71,11 @@ pub fn process_update_link( let link_account = next_account_info(accounts_iter)?; let contributor_account = next_account_info(accounts_iter)?; + let side_z_account: Option<&AccountInfo> = if accounts.len() > 5 { + Some(next_account_info(accounts_iter)?) + } else { + None + }; let globalstate_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; @@ -55,7 +84,11 @@ pub fn process_update_link( msg!("process_update_link({:?})", value); // Check if the payer is a signer - assert!(payer_account.is_signer, "Payer must be a signer"); + assert!( + payer_account.is_signer, + "Payer must be a signer {:?}", + payer_account + ); // Check the owner of the accounts assert_eq!(link_account.owner, program_id, "Invalid PDA Account Owner"); @@ -77,38 +110,77 @@ pub fn process_update_link( if contributor.owner != *payer_account.key && !globalstate.foundation_allowlist.contains(payer_account.key) { + msg!("contributor owner: {:?}", contributor.owner); return Err(DoubleZeroError::NotAllowed.into()); } + if let Some(side_z_account) = side_z_account { + if side_z_account.owner != program_id { + return Err(DoubleZeroError::InvalidAccountOwner.into()); + } + } + + // Deserialize the optional side_z device account + let side_z: Option = if let Some(side_z_account) = side_z_account { + Some(Device::try_from(side_z_account)?) + } else { + None + }; + // Deserialize the link account let mut link: Link = Link::try_from(link_account)?; - if let Some(ref code) = value.code { - link.code = validate_account_code(code).map_err(|_| DoubleZeroError::InvalidAccountCode)?; - } - if let Some(contributor_pk) = value.contributor_pk { - link.contributor_pk = contributor_pk; - } - if let Some(tunnel_type) = value.tunnel_type { - link.link_type = tunnel_type; - } - if let Some(bandwidth) = value.bandwidth { - link.bandwidth = bandwidth; - } - if let Some(mtu) = value.mtu { - link.mtu = mtu; - } - if let Some(delay_ns) = value.delay_ns { - link.delay_ns = delay_ns; + if side_z.is_none() { + // Link should be owned by the contributor A + if link.contributor_pk != *contributor_account.key { + msg!("link contributor_pk: {:?}", link.contributor_pk); + return Err(DoubleZeroError::NotAllowed.into()); + } + } else if let Some(side_z) = side_z { + // Link should be owned by the side_z device's contributor B + if link.side_z_pk != *side_z_account.unwrap().key { + return Err(DoubleZeroError::InvalidAccountOwner.into()); + } + if side_z.contributor_pk != *contributor_account.key { + msg!("side_z contributor_pk: {:?}", side_z.contributor_pk); + return Err(DoubleZeroError::NotAllowed.into()); + } } - if let Some(jitter_ns) = value.jitter_ns { - link.jitter_ns = jitter_ns; + + // can be updated by either contributor A or B + if link.contributor_pk == *contributor_account.key { + if let Some(ref code) = value.code { + link.code = + validate_account_code(code).map_err(|_| DoubleZeroError::InvalidAccountCode)?; + } + if let Some(tunnel_type) = value.tunnel_type { + link.link_type = tunnel_type; + } + if let Some(bandwidth) = value.bandwidth { + link.bandwidth = bandwidth; + } + if let Some(mtu) = value.mtu { + link.mtu = mtu; + } + if let Some(delay_ns) = value.delay_ns { + link.delay_ns = delay_ns; + } + if let Some(jitter_ns) = value.jitter_ns { + link.jitter_ns = jitter_ns; + } } + // Can be updated by both contributors A and B if let Some(delay_override_ns) = value.delay_override_ns { link.delay_override_ns = delay_override_ns; } + + // For now only allow foundation to update status if let Some(status) = value.status { // Only allow to update the status if the payer is in the foundation allowlist if !globalstate.foundation_allowlist.contains(payer_account.key) { + msg!( + "Payer is not in the foundation allowlist: {:?}", + payer_account.key + ); return Err(DoubleZeroError::NotAllowed.into()); } link.status = status; diff --git a/smartcontract/programs/doublezero-serviceability/tests/device_test.rs b/smartcontract/programs/doublezero-serviceability/tests/device_test.rs index a034c7954..4148efd44 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/device_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/device_test.rs @@ -208,6 +208,8 @@ async fn test_device() { vec![ AccountMeta::new(device_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_pubkey, false), + AccountMeta::new(location_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, @@ -340,6 +342,8 @@ async fn test_device() { vec![ AccountMeta::new(device_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_pubkey, false), + AccountMeta::new(location_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, @@ -492,6 +496,8 @@ async fn test_device_update_metrics_publisher_by_foundation_allowlist_account() vec![ AccountMeta::new(device_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_pubkey, false), + AccountMeta::new(location_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, @@ -525,6 +531,8 @@ async fn test_device_update_metrics_publisher_by_foundation_allowlist_account() vec![ AccountMeta::new(device_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_pubkey, false), + AccountMeta::new(location_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, diff --git a/smartcontract/programs/doublezero-serviceability/tests/device_update_location_test.rs b/smartcontract/programs/doublezero-serviceability/tests/device_update_location_test.rs new file mode 100644 index 000000000..3890082a5 --- /dev/null +++ b/smartcontract/programs/doublezero-serviceability/tests/device_update_location_test.rs @@ -0,0 +1,464 @@ +use device::activate::DeviceActivateArgs; +use doublezero_serviceability::{ + entrypoint::*, + instructions::*, + pda::*, + processors::{ + contributor::create::ContributorCreateArgs, + device::{closeaccount::*, create::*, delete::*, update::*}, + *, + }, + state::{accounttype::AccountType, contributor::ContributorStatus, device::*}, +}; +use globalconfig::set::SetGlobalConfigArgs; +use solana_program_test::*; +use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signer::Signer}; + +mod test_helpers; +use test_helpers::*; + +#[tokio::test] +async fn device_update_location_test() { + let program_id = Pubkey::new_unique(); + let (mut banks_client, payer, recent_blockhash) = ProgramTest::new( + "doublezero_serviceability", + program_id, + processor!(process_instruction), + ) + .start() + .await; + + /***********************************************************************************************************************************/ + println!("🟒 Start test_device"); + let (program_config_pubkey, _) = get_program_config_pda(&program_id); + let (globalstate_pubkey, _) = get_globalstate_pda(&program_id); + + /***********************************************************************************************************************************/ + println!("🟒 1. Global Initialization..."); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::InitGlobalState(), + vec![ + AccountMeta::new(program_config_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + /***********************************************************************************************************************************/ + println!("🟒 2. Set GlobalConfig..."); + let (config_pubkey, _) = get_globalconfig_pda(&program_id); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::SetGlobalConfig(SetGlobalConfigArgs { + local_asn: 65000, + remote_asn: 65001, + device_tunnel_block: "10.0.0.0/24".parse().unwrap(), // Private tunnel block + user_tunnel_block: "10.0.0.0/24".parse().unwrap(), // Private tunnel block + multicastgroup_block: "224.0.0.0/4".parse().unwrap(), // Multicast block + next_bgp_community: None, + }), + vec![ + AccountMeta::new(config_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + /***********************************************************************************************************************************/ + println!("🟒 3. Create Location..."); + let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; + assert_eq!(globalstate_account.account_index, 0); + + let (location_la_pubkey, _) = + get_location_pda(&program_id, globalstate_account.account_index + 1); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::CreateLocation(location::create::LocationCreateArgs { + code: "la".to_string(), + name: "Los Angeles".to_string(), + country: "us".to_string(), + lat: 1.234, + lng: 4.567, + loc_id: 0, + }), + vec![ + AccountMeta::new(location_la_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + /***********************************************************************************************************************************/ + println!("🟒 4. Create Location..."); + let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; + assert_eq!(globalstate_account.account_index, 1); + + let (location_ny_pubkey, _) = + get_location_pda(&program_id, globalstate_account.account_index + 1); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::CreateLocation(location::create::LocationCreateArgs { + code: "ny".to_string(), + name: "New York".to_string(), + country: "us".to_string(), + lat: 1.234, + lng: 4.567, + loc_id: 0, + }), + vec![ + AccountMeta::new(location_ny_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + /***********************************************************************************************************************************/ + println!("🟒 5. Create Exchange..."); + let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; + assert_eq!(globalstate_account.account_index, 2); + + let (exchange_pubkey, _) = get_exchange_pda(&program_id, globalstate_account.account_index + 1); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::CreateExchange(exchange::create::ExchangeCreateArgs { + code: "la".to_string(), + name: "Los Angeles".to_string(), + lat: 1.234, + lng: 4.567, + reserved: 0, + }), + vec![ + AccountMeta::new(exchange_pubkey, false), + AccountMeta::new(config_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + /***********************************************************************************************************************************/ + println!("🟒 6. Create Contributor..."); + let (globalstate_pubkey, _) = get_globalstate_pda(&program_id); + let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; + assert_eq!(globalstate_account.account_index, 3); + + let (contributor_pubkey, _) = + get_contributor_pda(&program_id, globalstate_account.account_index + 1); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::CreateContributor(ContributorCreateArgs { + code: "cont".to_string(), + }), + vec![ + AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(payer.pubkey(), false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let contributor = get_account_data(&mut banks_client, contributor_pubkey) + .await + .expect("Unable to get Account") + .get_contributor() + .unwrap(); + assert_eq!(contributor.account_type, AccountType::Contributor); + assert_eq!(contributor.code, "cont".to_string()); + assert_eq!(contributor.reference_count, 0); + assert_eq!(contributor.status, ContributorStatus::Activated); + + println!("βœ… Contributor initialized successfully",); + /***********************************************************************************************************************************/ + // Device _la + println!("🟒 7. Create Device..."); + let (globalstate_pubkey, _) = get_globalstate_pda(&program_id); + + let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; + assert_eq!(globalstate_account.account_index, 4); + + let (device_pubkey, _) = get_device_pda(&program_id, globalstate_account.account_index + 1); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::CreateDevice(DeviceCreateArgs { + code: "la".to_string(), + device_type: DeviceType::Switch, + public_ip: [8, 8, 8, 8].into(), // Global public IP + dz_prefixes: "110.1.0.0/23".parse().unwrap(), // Global prefix + metrics_publisher_pk: Pubkey::default(), + mgmt_vrf: "mgmt".to_string(), + }), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_la_pubkey, false), + AccountMeta::new(exchange_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let device = get_account_data(&mut banks_client, device_pubkey) + .await + .expect("Unable to get Account") + .get_device() + .unwrap(); + assert_eq!(device.account_type, AccountType::Device); + assert_eq!(device.code, "la".to_string()); + assert_eq!(device.status, DeviceStatus::Pending); + + println!("βœ… Device Created successfully",); + /***********************************************************************************************************************************/ + // Device _la + println!("🟒 8. Update Device ..."); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::UpdateDevice(DeviceUpdateArgs { + max_users: Some(128), + ..DeviceUpdateArgs::default() + }), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_la_pubkey, false), + AccountMeta::new(location_ny_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let device_la = get_account_data(&mut banks_client, device_pubkey) + .await + .expect("Unable to get Device") + .get_device() + .unwrap(); + assert_eq!(device_la.max_users, 128); + assert_eq!(device_la.location_pk, location_ny_pubkey); + + // check reference counts + let contributor = get_account_data(&mut banks_client, contributor_pubkey) + .await + .expect("Unable to get Account") + .get_contributor() + .unwrap(); + assert_eq!(contributor.reference_count, 1); + //check reference counts + let location_la = get_account_data(&mut banks_client, location_la_pubkey) + .await + .expect("Unable to get Account") + .get_location() + .unwrap(); + assert_eq!(location_la.reference_count, 0); + //check reference counts + let location_ny = get_account_data(&mut banks_client, location_ny_pubkey) + .await + .expect("Unable to get Account") + .get_location() + .unwrap(); + assert_eq!(location_ny.reference_count, 1); + //check reference counts + let exchange = get_account_data(&mut banks_client, exchange_pubkey) + .await + .expect("Unable to get Account") + .get_exchange() + .unwrap(); + assert_eq!(exchange.reference_count, 1); + + println!("βœ… Device initialized successfully",); + /*****************************************************************************************************************************************************/ + println!("🟒 9. Activate Device..."); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::ActivateDevice(DeviceActivateArgs {}), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let device = get_account_data(&mut banks_client, device_pubkey) + .await + .expect("Unable to get Account") + .get_device() + .unwrap(); + assert_eq!(device.account_type, AccountType::Device); + assert_eq!(device.code, "la".to_string()); + assert_eq!(device.status, DeviceStatus::Activated); + + println!("βœ… Device updated"); + /*****************************************************************************************************************************************************/ + println!("🟒 10. Update Device..."); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::UpdateDevice(DeviceUpdateArgs { + code: None, + device_type: None, + contributor_pk: None, + public_ip: None, + dz_prefixes: None, + metrics_publisher_pk: None, + mgmt_vrf: None, + max_users: None, + users_count: None, + }), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_ny_pubkey, false), + AccountMeta::new(location_ny_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let device_la = get_account_data(&mut banks_client, device_pubkey) + .await + .expect("Unable to get Account") + .get_device() + .unwrap(); + assert_eq!(device_la.account_type, AccountType::Device); + assert_eq!(device_la.code, "la".to_string()); + assert_eq!(device_la.location_pk, location_ny_pubkey); + assert_eq!(device_la.status, DeviceStatus::Activated); + + println!("βœ… Device updated"); + /*****************************************************************************************************************************************************/ + println!("🟒 11. Update Device fail..."); + let res = try_execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::UpdateDevice(DeviceUpdateArgs { + code: None, + device_type: None, + contributor_pk: None, + public_ip: None, + dz_prefixes: None, + metrics_publisher_pk: None, + mgmt_vrf: None, + max_users: None, + users_count: None, + }), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_la_pubkey, false), + AccountMeta::new(location_ny_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + assert!(res.is_err()); + + println!("βœ… Device update failed correctly."); + /*****************************************************************************************************************************************************/ + println!("🟒 12. Deleting Device..."); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::DeleteDevice(DeviceDeleteArgs {}), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let device_la = get_account_data(&mut banks_client, device_pubkey) + .await + .expect("Unable to get Account") + .get_device() + .unwrap(); + assert_eq!(device_la.account_type, AccountType::Device); + assert_eq!(device_la.code, "la".to_string()); + assert_eq!(device_la.public_ip.to_string(), "8.8.8.8"); + assert_eq!(device_la.status, DeviceStatus::Deleting); + + /*****************************************************************************************************************************************************/ + println!("🟒 13. CloseAccount Device..."); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::CloseAccountDevice(DeviceCloseAccountArgs {}), + vec![ + AccountMeta::new(device_pubkey, false), + AccountMeta::new(device.owner, false), + AccountMeta::new(device.contributor_pk, false), + AccountMeta::new(device.location_pk, false), + AccountMeta::new(device.exchange_pk, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer, + ) + .await; + + let device_la = get_account_data(&mut banks_client, device_pubkey).await; + assert_eq!(device_la, None); + + // check reference counts + let contributor = get_account_data(&mut banks_client, contributor_pubkey) + .await + .expect("Unable to get Account") + .get_contributor() + .unwrap(); + assert_eq!(contributor.reference_count, 0); + //check reference counts + let location = get_account_data(&mut banks_client, location_la_pubkey) + .await + .expect("Unable to get Account") + .get_location() + .unwrap(); + assert_eq!(location.reference_count, 0); + //check reference counts + let exchange = get_account_data(&mut banks_client, exchange_pubkey) + .await + .expect("Unable to get Account") + .get_exchange() + .unwrap(); + assert_eq!(exchange.reference_count, 0); + + println!("βœ… Device deleted successfully"); + println!("🟒🟒🟒 End test_device 🟒🟒🟒"); +} diff --git a/smartcontract/programs/doublezero-serviceability/tests/exchange_setdevice.rs b/smartcontract/programs/doublezero-serviceability/tests/exchange_setdevice.rs index 948fdd99c..5675afb07 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/exchange_setdevice.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/exchange_setdevice.rs @@ -206,6 +206,8 @@ async fn exchange_setdevice() { vec![ AccountMeta::new(device_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_pubkey, false), + AccountMeta::new(location_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, diff --git a/smartcontract/programs/doublezero-serviceability/tests/global_test.rs b/smartcontract/programs/doublezero-serviceability/tests/global_test.rs index 1277ee450..3b9671904 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/global_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/global_test.rs @@ -350,6 +350,8 @@ async fn test_doublezero_program() { vec![ AccountMeta::new(device_la_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_la_pubkey, false), + AccountMeta::new(location_la_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, @@ -469,6 +471,8 @@ async fn test_doublezero_program() { vec![ AccountMeta::new(device_ny_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_ny_pubkey, false), + AccountMeta::new(location_ny_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, diff --git a/smartcontract/programs/doublezero-serviceability/tests/link_dzx_test.rs b/smartcontract/programs/doublezero-serviceability/tests/link_dzx_test.rs index d3b5a4272..1cd2836a2 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/link_dzx_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/link_dzx_test.rs @@ -2,6 +2,7 @@ use doublezero_serviceability::{ instructions::*, pda::*, processors::{ + allowlist::foundation::add::AddFoundationAllowlistArgs, contributor::create::ContributorCreateArgs, device::interface::DeviceInterfaceUnlinkArgs, link::{ @@ -168,6 +169,22 @@ async fn test_dzx_link() { let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; assert_eq!(globalstate_account.account_index, 3); + let payer2 = solana_sdk::signer::keypair::Keypair::new(); + transfer(&mut banks_client, &payer, &payer2.pubkey(), 10_000_000_000).await; + + println!("Add to allowlist..."); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::AddFoundationAllowlist(AddFoundationAllowlistArgs { + pubkey: payer2.pubkey(), + }), + vec![AccountMeta::new(globalstate_pubkey, false)], + &payer, + ) + .await; + let (contributor2_pubkey, _) = get_contributor_pda(&program_id, globalstate_account.account_index + 1); @@ -180,10 +197,10 @@ async fn test_dzx_link() { }), vec![ AccountMeta::new(contributor2_pubkey, false), - AccountMeta::new(payer.pubkey(), false), + AccountMeta::new(payer2.pubkey(), false), AccountMeta::new(globalstate_pubkey, false), ], - &payer, + &payer2, ) .await; @@ -313,7 +330,7 @@ async fn test_dzx_link() { AccountMeta::new(exchange_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], - &payer, + &payer2, ) .await; @@ -340,7 +357,7 @@ async fn test_dzx_link() { AccountMeta::new(contributor2_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], - &payer, + &payer2, ) .await; @@ -423,7 +440,7 @@ async fn test_dzx_link() { let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await; assert_eq!(globalstate_account.account_index, 6); - let (tunnel_pubkey, _) = get_link_pda(&program_id, globalstate_account.account_index + 1); + let (link_dzx_pubkey, _) = get_link_pda(&program_id, globalstate_account.account_index + 1); execute_transaction( &mut banks_client, @@ -440,7 +457,7 @@ async fn test_dzx_link() { side_z_iface_name: None, }), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor1_pubkey, false), AccountMeta::new(device_a_pubkey, false), AccountMeta::new(device_z_pubkey, false), @@ -450,7 +467,7 @@ async fn test_dzx_link() { ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -494,7 +511,7 @@ async fn test_dzx_link() { side_z_iface_name: "Ethernet1".to_string(), }), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor1_pubkey, false), AccountMeta::new(device_z_pubkey, false), AccountMeta::new(globalstate_pubkey, false), @@ -505,7 +522,7 @@ async fn test_dzx_link() { assert!(res.is_err()); - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -525,16 +542,16 @@ async fn test_dzx_link() { side_z_iface_name: "Ethernet1".to_string(), }), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor2_pubkey, false), AccountMeta::new(device_z_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], - &payer, + &payer2, ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -555,7 +572,7 @@ async fn test_dzx_link() { tunnel_net: "10.0.0.0/21".parse().unwrap(), }), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(device_a_pubkey, false), AccountMeta::new(device_z_pubkey, false), AccountMeta::new(globalstate_pubkey, false), @@ -564,7 +581,7 @@ async fn test_dzx_link() { ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -583,7 +600,7 @@ async fn test_dzx_link() { program_id, DoubleZeroInstruction::SuspendLink(LinkSuspendArgs {}), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor1_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], @@ -591,7 +608,7 @@ async fn test_dzx_link() { ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -608,7 +625,7 @@ async fn test_dzx_link() { program_id, DoubleZeroInstruction::ResumeLink(LinkResumeArgs {}), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor1_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], @@ -616,7 +633,7 @@ async fn test_dzx_link() { ) .await; - let link = get_account_data(&mut banks_client, tunnel_pubkey) + let link = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -643,7 +660,7 @@ async fn test_dzx_link() { status: None, }), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor1_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], @@ -651,7 +668,7 @@ async fn test_dzx_link() { ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -666,14 +683,48 @@ async fn test_dzx_link() { println!("βœ… Link updated"); /*****************************************************************************************************************************************************/ - println!("🟒 14. Deleting Link..."); + println!("🟒 14. Update Link by Contributor B..."); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::UpdateLink(LinkUpdateArgs { + delay_override_ns: Some(500000), + ..Default::default() + }), + vec![ + AccountMeta::new(link_dzx_pubkey, false), + AccountMeta::new(contributor2_pubkey, false), + AccountMeta::new(device_z_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + ], + &payer2, + ) + .await; + + let link_dzx = get_account_data(&mut banks_client, link_dzx_pubkey) + .await + .expect("Unable to get Account") + .get_tunnel() + .unwrap(); + assert_eq!(link_dzx.account_type, AccountType::Link); + assert_eq!(link_dzx.code, "la2".to_string()); + assert_eq!(link_dzx.bandwidth, 20000000000); + assert_eq!(link_dzx.mtu, 8900); + assert_eq!(link_dzx.delay_ns, 1000000); + assert_eq!(link_dzx.status, LinkStatus::Activated); + + println!("βœ… Link updated"); + + /*****************************************************************************************************************************************************/ + println!("🟒 15. Deleting Link..."); execute_transaction( &mut banks_client, recent_blockhash, program_id, DoubleZeroInstruction::DeleteLink(LinkDeleteArgs {}), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(contributor1_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], @@ -681,7 +732,7 @@ async fn test_dzx_link() { ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey) + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey) .await .expect("Unable to get Account") .get_tunnel() @@ -696,14 +747,14 @@ async fn test_dzx_link() { println!("βœ… Link deleting"); /*****************************************************************************************************************************************************/ - println!("🟒 15. CloseAccount Link..."); + println!("🟒 16. CloseAccount Link..."); execute_transaction( &mut banks_client, recent_blockhash, program_id, DoubleZeroInstruction::CloseAccountLink(LinkCloseAccountArgs {}), vec![ - AccountMeta::new(tunnel_pubkey, false), + AccountMeta::new(link_dzx_pubkey, false), AccountMeta::new(link.owner, false), AccountMeta::new(link.contributor_pk, false), AccountMeta::new(link.side_a_pk, false), @@ -714,7 +765,7 @@ async fn test_dzx_link() { ) .await; - let tunnel_la = get_account_data(&mut banks_client, tunnel_pubkey).await; + let tunnel_la = get_account_data(&mut banks_client, link_dzx_pubkey).await; assert_eq!(tunnel_la, None); // check reference counts diff --git a/smartcontract/programs/doublezero-serviceability/tests/test_helpers.rs b/smartcontract/programs/doublezero-serviceability/tests/test_helpers.rs index 762cbcf17..4b57f2b64 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/test_helpers.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/test_helpers.rs @@ -16,6 +16,7 @@ use std::any::type_name; // Use a fixed byte array to create a constant Keypair for testing // This is safe for tests only; never use hardcoded keys in production! +#[allow(dead_code)] pub const TEST_PAYER_BYTES: [u8; 64] = [ 169, 191, 120, 114, 135, 172, 221, 186, 245, 154, 139, 162, 103, 229, 16, 1, 170, 160, 159, 47, 224, 60, 179, 71, 245, 255, 116, 238, 144, 208, 19, 89, 13, 59, 115, 1, 186, 171, 180, 37, 165, @@ -23,10 +24,12 @@ pub const TEST_PAYER_BYTES: [u8; 64] = [ 241, 90, ]; +#[allow(dead_code)] pub fn test_payer() -> Keypair { Keypair::from_bytes(&TEST_PAYER_BYTES).unwrap() } +#[allow(dead_code)] pub async fn init_test() -> (BanksClient, Pubkey, Keypair, solana_program::hash::Hash) { let program_id = Pubkey::new_unique(); @@ -142,7 +145,6 @@ pub async fn execute_transaction( .get_latest_blockhash() .await .expect("Failed to get latest blockhash"); - println!("recent_blockhash: {recent_blockhash} "); let mut transaction = create_transaction(program_id, &instruction, &accounts, payer); transaction.try_sign(&[&payer], recent_blockhash).unwrap(); banks_client.process_transaction(transaction).await.unwrap(); @@ -159,13 +161,6 @@ async fn execute_transaction_tester( ) -> Result<(), BanksClientError> { let test_payer = Pubkey::new_unique(); - let recent_blockhash = banks_client - .get_latest_blockhash() - .await - .expect("Failed to get latest blockhash"); - - println!("recent_blockhash: {recent_blockhash} "); - let recent_blockhash = banks_client .get_latest_blockhash() .await diff --git a/smartcontract/programs/doublezero-serviceability/tests/user_tests.rs b/smartcontract/programs/doublezero-serviceability/tests/user_tests.rs index 126e91ece..e77bfc012 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/user_tests.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/user_tests.rs @@ -214,6 +214,8 @@ async fn test_user() { vec![ AccountMeta::new(device_pubkey, false), AccountMeta::new(contributor_pubkey, false), + AccountMeta::new(location_pubkey, false), + AccountMeta::new(location_pubkey, false), AccountMeta::new(globalstate_pubkey, false), ], &payer, diff --git a/smartcontract/sdk/rs/src/commands/device/update.rs b/smartcontract/sdk/rs/src/commands/device/update.rs index 63248418b..f0a3d1ca5 100644 --- a/smartcontract/sdk/rs/src/commands/device/update.rs +++ b/smartcontract/sdk/rs/src/commands/device/update.rs @@ -20,6 +20,7 @@ pub struct UpdateDeviceCommand { pub dz_prefixes: Option, pub metrics_publisher: Option, pub contributor_pk: Option, + pub location_pk: Option, pub mgmt_vrf: Option, pub interfaces: Option>, pub max_users: Option, @@ -59,6 +60,8 @@ impl UpdateDeviceCommand { vec![ AccountMeta::new(self.pubkey, false), AccountMeta::new(device.contributor_pk, false), + AccountMeta::new(device.location_pk, false), + AccountMeta::new(self.location_pk.unwrap_or(device.location_pk), false), AccountMeta::new(globalstate_pubkey, false), ], ) @@ -143,6 +146,7 @@ mod tests { dz_prefixes: Some("10.0.0.0/8".parse().unwrap()), metrics_publisher: None, mgmt_vrf: Some("mgmt".to_string()), + location_pk: None, interfaces: None, max_users: None, users_count: None,