Skip to content

Commit 935a9b7

Browse files
committed
add location support to device update functionality
update device location instruction for reference counter management
1 parent 7c872b7 commit 935a9b7

File tree

13 files changed

+735
-65
lines changed

13 files changed

+735
-65
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.
66

77
### Breaking
88

9+
- Updated the device update command to allow modifying a device’s location.
10+
911
### Changes
1012
- RFCs
1113
- RFC9 Link Draining
@@ -82,6 +84,7 @@ All notable changes to this project will be documented in this file.
8284
- serviceability: prevent device interface name duplication
8385
- Update serviceability and telemetry program instruction args to use the `BorshDeserializeIncremental` derive macro incremental, backward-compatible, deserialization of structs.
8486
- Add explicit signer checks for payer accounts across various processors to improve security and ensure correct transaction authorization.
87+
- Add the ability to update a Device’s location, managing the reference counters accordingly.
8588
- CLI
8689
- Removed `--bgp-community` option from `doublezero exchange create` since these values are now assigned automatically
8790
- Add `--next-bgp-community` option to `doublezero global-config set` so authorized users can control which bgp_community will be assigned next

smartcontract/cli/src/device/update.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub struct UpdateDeviceCliCommand {
3535
/// Contributor Pubkey (optional)
3636
#[arg(long, value_parser = validate_pubkey)]
3737
pub contributor: Option<String>,
38+
/// Location Pubkey (optional)
39+
#[arg(long, value_parser = validate_pubkey)]
40+
pub location: Option<String>,
3841
/// Management VRF name (optional)
3942
#[arg(long)]
4043
pub mgmt_vrf: Option<String>,
@@ -113,6 +116,10 @@ impl UpdateDeviceCliCommand {
113116
dz_prefixes: self.dz_prefixes,
114117
metrics_publisher,
115118
contributor_pk: contributor,
119+
location_pk: match &self.location {
120+
Some(location) => Some(Pubkey::from_str(location)?),
121+
None => None,
122+
},
116123
mgmt_vrf: self.mgmt_vrf,
117124
interfaces: None,
118125
max_users: self.max_users,
@@ -258,6 +265,9 @@ mod tests {
258265
contributor_pk: Some(Pubkey::from_str_const(
259266
"HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx",
260267
)),
268+
location_pk: Some(Pubkey::from_str_const(
269+
"HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx",
270+
)),
261271
mgmt_vrf: Some("default".to_string()),
262272
interfaces: None,
263273
max_users: Some(1025),
@@ -275,6 +285,7 @@ mod tests {
275285
dz_prefixes: Some("1.2.3.4/32".parse().unwrap()),
276286
metrics_publisher: Some("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx".to_string()),
277287
contributor: Some("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx".to_string()),
288+
location: Some("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx".to_string()),
278289
mgmt_vrf: Some("default".to_string()),
279290
max_users: Some(1025),
280291
users_count: Some(0),
@@ -364,6 +375,7 @@ mod tests {
364375
public_ip: None,
365376
dz_prefixes: None,
366377
metrics_publisher: None,
378+
location: None,
367379
contributor: None,
368380
mgmt_vrf: None,
369381
max_users: Some(255),
@@ -453,6 +465,7 @@ mod tests {
453465
public_ip: Some([10, 20, 30, 40].into()),
454466
dz_prefixes: None,
455467
metrics_publisher: None,
468+
location: None,
456469
contributor: None,
457470
mgmt_vrf: None,
458471
max_users: None,

smartcontract/programs/doublezero-serviceability/src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ pub enum DoubleZeroError {
121121
InvalidInterfaceType, // variant 57
122122
#[error("Invalid Loopback Type")]
123123
InvalidLoopbackType, // variant 58
124+
#[error("Invalid Actual Location")]
125+
InvalidActualLocation, // variant 59
124126
}
125127

126128
impl From<DoubleZeroError> for ProgramError {
@@ -185,6 +187,7 @@ impl From<DoubleZeroError> for ProgramError {
185187
DoubleZeroError::InterfaceAlreadyExists => ProgramError::Custom(56),
186188
DoubleZeroError::InvalidInterfaceType => ProgramError::Custom(57),
187189
DoubleZeroError::InvalidLoopbackType => ProgramError::Custom(58),
190+
DoubleZeroError::InvalidActualLocation => ProgramError::Custom(59),
188191
}
189192
}
190193
}
@@ -250,6 +253,7 @@ impl From<u32> for DoubleZeroError {
250253
56 => DoubleZeroError::InterfaceAlreadyExists,
251254
57 => DoubleZeroError::InvalidInterfaceType,
252255
58 => DoubleZeroError::InvalidLoopbackType,
256+
59 => DoubleZeroError::InvalidActualLocation,
253257
_ => DoubleZeroError::Custom(e),
254258
}
255259
}
@@ -335,6 +339,7 @@ mod tests {
335339
InterfaceAlreadyExists,
336340
InvalidInterfaceType,
337341
InvalidLoopbackType,
342+
InvalidActualLocation,
338343
];
339344
for err in variants {
340345
let pe: ProgramError = err.clone().into();

smartcontract/programs/doublezero-serviceability/src/processors/device/update.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ use crate::{
22
error::DoubleZeroError,
33
globalstate::globalstate_get,
44
helper::*,
5-
state::{accounttype::AccountType, contributor::Contributor, device::*},
5+
state::{accounttype::AccountType, contributor::Contributor, device::*, location::Location},
66
};
77
use borsh::BorshSerialize;
88
use borsh_incremental::BorshDeserializeIncremental;
99
use core::fmt;
1010
use doublezero_program_common::{types::NetworkV4List, validate_account_code};
11-
#[cfg(test)]
12-
use solana_program::msg;
1311
use solana_program::{
1412
account_info::{next_account_info, AccountInfo},
1513
entrypoint::ProgramResult,
14+
msg,
1615
pubkey::Pubkey,
1716
};
1817

@@ -71,6 +70,17 @@ pub fn process_update_device(
7170

7271
let device_account = next_account_info(accounts_iter)?;
7372
let contributor_account = next_account_info(accounts_iter)?;
73+
// Update location accounts (old and new)
74+
75+
let (location_old_account, location_new_account) = if accounts.len() == 7 {
76+
(
77+
Some(next_account_info(accounts_iter)?),
78+
Some(next_account_info(accounts_iter)?),
79+
)
80+
} else {
81+
(None, None)
82+
};
83+
7484
let globalstate_account = next_account_info(accounts_iter)?;
7585
let payer_account = next_account_info(accounts_iter)?;
7686
let system_program = next_account_info(accounts_iter)?;
@@ -148,6 +158,43 @@ pub fn process_update_device(
148158
device.max_users = max_users;
149159
}
150160

161+
// Handle location update if both old and new location accounts are provided
162+
if let (Some(location_old_account), Some(location_new_account)) =
163+
(location_old_account, location_new_account)
164+
{
165+
if location_old_account.key != location_new_account.key {
166+
let mut location_old = Location::try_from(location_old_account)?;
167+
let mut location_new = Location::try_from(location_new_account)?;
168+
if device.location_pk != *location_old_account.key {
169+
msg!(
170+
"Invalid location account. Device location_pk: {}, location_old_account: {}",
171+
device.location_pk,
172+
location_old_account.key
173+
);
174+
return Err(DoubleZeroError::InvalidActualLocation.into());
175+
}
176+
177+
location_old.reference_count = location_old.reference_count.saturating_sub(1);
178+
location_new.reference_count = location_new.reference_count.saturating_add(1);
179+
180+
// Set new location pk in device
181+
device.location_pk = *location_new_account.key;
182+
183+
account_write(
184+
location_old_account,
185+
&location_old,
186+
payer_account,
187+
system_program,
188+
)?;
189+
account_write(
190+
location_new_account,
191+
&location_new,
192+
payer_account,
193+
system_program,
194+
)?;
195+
}
196+
}
197+
151198
account_write(device_account, &device, payer_account, system_program)?;
152199

153200
#[cfg(test)]

smartcontract/programs/doublezero-serviceability/src/processors/link/update.rs

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ use crate::{
22
error::DoubleZeroError,
33
globalstate::globalstate_get,
44
helper::*,
5-
state::{contributor::Contributor, link::*},
5+
state::{contributor::Contributor, device::Device, link::*},
66
};
77
use borsh::BorshSerialize;
88
use borsh_incremental::BorshDeserializeIncremental;
99
use core::fmt;
1010
use doublezero_program_common::validate_account_code;
11-
#[cfg(test)]
12-
use solana_program::msg;
11+
1312
use solana_program::{
1413
account_info::{next_account_info, AccountInfo},
1514
entrypoint::ProgramResult,
15+
msg,
1616
pubkey::Pubkey,
1717
};
1818
#[derive(BorshSerialize, BorshDeserializeIncremental, PartialEq, Clone, Default)]
@@ -30,11 +30,35 @@ pub struct LinkUpdateArgs {
3030

3131
impl fmt::Debug for LinkUpdateArgs {
3232
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33-
write!(
34-
f,
35-
"code: {:?}, tunnel_type: {:?}, bandwidth: {:?}, mtu: {:?}, delay_ns: {:?}, jitter_ns: {:?}, delay_override_ns: {:?}",
36-
self.code, self.tunnel_type, self.bandwidth, self.mtu, self.delay_ns, self.jitter_ns, self.delay_override_ns
37-
)
33+
let mut parts = Vec::new();
34+
if let Some(ref code) = self.code {
35+
parts.push(format!("code: {:?}", code));
36+
}
37+
if let Some(ref contributor_pk) = self.contributor_pk {
38+
parts.push(format!("contributor_pk: {:?}", contributor_pk));
39+
}
40+
if let Some(ref tunnel_type) = self.tunnel_type {
41+
parts.push(format!("tunnel_type: {:?}", tunnel_type));
42+
}
43+
if let Some(bandwidth) = self.bandwidth {
44+
parts.push(format!("bandwidth: {:?}", bandwidth));
45+
}
46+
if let Some(mtu) = self.mtu {
47+
parts.push(format!("mtu: {:?}", mtu));
48+
}
49+
if let Some(delay_ns) = self.delay_ns {
50+
parts.push(format!("delay_ns: {:?}", delay_ns));
51+
}
52+
if let Some(jitter_ns) = self.jitter_ns {
53+
parts.push(format!("jitter_ns: {:?}", jitter_ns));
54+
}
55+
if let Some(ref status) = self.status {
56+
parts.push(format!("status: {:?}", status));
57+
}
58+
if let Some(delay_override_ns) = self.delay_override_ns {
59+
parts.push(format!("delay_override_ns: {:?}", delay_override_ns));
60+
}
61+
write!(f, "{}", parts.join(", "))
3862
}
3963
}
4064

@@ -47,6 +71,11 @@ pub fn process_update_link(
4771

4872
let link_account = next_account_info(accounts_iter)?;
4973
let contributor_account = next_account_info(accounts_iter)?;
74+
let side_z_account: Option<&AccountInfo> = if accounts.len() > 5 {
75+
Some(next_account_info(accounts_iter)?)
76+
} else {
77+
None
78+
};
5079
let globalstate_account = next_account_info(accounts_iter)?;
5180
let payer_account = next_account_info(accounts_iter)?;
5281
let system_program = next_account_info(accounts_iter)?;
@@ -55,7 +84,11 @@ pub fn process_update_link(
5584
msg!("process_update_link({:?})", value);
5685

5786
// Check if the payer is a signer
58-
assert!(payer_account.is_signer, "Payer must be a signer");
87+
assert!(
88+
payer_account.is_signer,
89+
"Payer must be a signer {:?}",
90+
payer_account
91+
);
5992

6093
// Check the owner of the accounts
6194
assert_eq!(link_account.owner, program_id, "Invalid PDA Account Owner");
@@ -77,38 +110,77 @@ pub fn process_update_link(
77110
if contributor.owner != *payer_account.key
78111
&& !globalstate.foundation_allowlist.contains(payer_account.key)
79112
{
113+
msg!("contributor owner: {:?}", contributor.owner);
80114
return Err(DoubleZeroError::NotAllowed.into());
81115
}
116+
if let Some(side_z_account) = side_z_account {
117+
if side_z_account.owner != program_id {
118+
return Err(DoubleZeroError::InvalidAccountOwner.into());
119+
}
120+
}
121+
122+
// Deserialize the optional side_z device account
123+
let side_z: Option<Device> = if let Some(side_z_account) = side_z_account {
124+
Some(Device::try_from(side_z_account)?)
125+
} else {
126+
None
127+
};
82128

129+
// Deserialize the link account
83130
let mut link: Link = Link::try_from(link_account)?;
84131

85-
if let Some(ref code) = value.code {
86-
link.code = validate_account_code(code).map_err(|_| DoubleZeroError::InvalidAccountCode)?;
87-
}
88-
if let Some(contributor_pk) = value.contributor_pk {
89-
link.contributor_pk = contributor_pk;
90-
}
91-
if let Some(tunnel_type) = value.tunnel_type {
92-
link.link_type = tunnel_type;
93-
}
94-
if let Some(bandwidth) = value.bandwidth {
95-
link.bandwidth = bandwidth;
96-
}
97-
if let Some(mtu) = value.mtu {
98-
link.mtu = mtu;
99-
}
100-
if let Some(delay_ns) = value.delay_ns {
101-
link.delay_ns = delay_ns;
132+
if side_z.is_none() {
133+
// Link should be owned by the contributor A
134+
if link.contributor_pk != *contributor_account.key {
135+
msg!("link contributor_pk: {:?}", link.contributor_pk);
136+
return Err(DoubleZeroError::NotAllowed.into());
137+
}
138+
} else if let Some(side_z) = side_z {
139+
// Link should be owned by the side_z device's contributor B
140+
if link.side_z_pk != *side_z_account.unwrap().key {
141+
return Err(DoubleZeroError::InvalidAccountOwner.into());
142+
}
143+
if side_z.contributor_pk != *contributor_account.key {
144+
msg!("side_z contributor_pk: {:?}", side_z.contributor_pk);
145+
return Err(DoubleZeroError::NotAllowed.into());
146+
}
102147
}
103-
if let Some(jitter_ns) = value.jitter_ns {
104-
link.jitter_ns = jitter_ns;
148+
149+
// can be updated by either contributor A or B
150+
if link.contributor_pk == *contributor_account.key {
151+
if let Some(ref code) = value.code {
152+
link.code =
153+
validate_account_code(code).map_err(|_| DoubleZeroError::InvalidAccountCode)?;
154+
}
155+
if let Some(tunnel_type) = value.tunnel_type {
156+
link.link_type = tunnel_type;
157+
}
158+
if let Some(bandwidth) = value.bandwidth {
159+
link.bandwidth = bandwidth;
160+
}
161+
if let Some(mtu) = value.mtu {
162+
link.mtu = mtu;
163+
}
164+
if let Some(delay_ns) = value.delay_ns {
165+
link.delay_ns = delay_ns;
166+
}
167+
if let Some(jitter_ns) = value.jitter_ns {
168+
link.jitter_ns = jitter_ns;
169+
}
105170
}
171+
// Can be updated by both contributors A and B
106172
if let Some(delay_override_ns) = value.delay_override_ns {
107173
link.delay_override_ns = delay_override_ns;
108174
}
175+
176+
// For now only allow foundation to update status
109177
if let Some(status) = value.status {
110178
// Only allow to update the status if the payer is in the foundation allowlist
111179
if !globalstate.foundation_allowlist.contains(payer_account.key) {
180+
msg!(
181+
"Payer is not in the foundation allowlist: {:?}",
182+
payer_account.key
183+
);
112184
return Err(DoubleZeroError::NotAllowed.into());
113185
}
114186
link.status = status;

0 commit comments

Comments
 (0)