Skip to content

Commit 9b67b5f

Browse files
serviceability: add owner field to UpdateMulticastGroup instruction
1 parent ad91e7f commit 9b67b5f

6 files changed

Lines changed: 221 additions & 34 deletions

File tree

smartcontract/cli/src/multicastgroup/update.rs

Lines changed: 197 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use crate::{
22
doublezerocommand::CliCommand,
33
poll_for_activation::poll_for_multicastgroup_activated,
44
requirements::{CHECK_BALANCE, CHECK_ID_JSON},
5-
validators::{validate_code, validate_parse_bandwidth, validate_pubkey_or_code},
5+
validators::{
6+
validate_code, validate_parse_bandwidth, validate_pubkey, validate_pubkey_or_code,
7+
},
68
};
79
use clap::Args;
810
use doublezero_sdk::commands::multicastgroup::{
@@ -30,6 +32,9 @@ pub struct UpdateMulticastGroupCliCommand {
3032
/// Updated subscriber count
3133
#[arg(long)]
3234
pub subscriber_count: Option<u32>,
35+
/// Updated owner pubkey for the multicast group
36+
#[arg(long, value_parser = validate_pubkey)]
37+
pub owner: Option<String>,
3338
/// Wait for the multicast group to be activated
3439
#[arg(short, long, default_value_t = false)]
3540
pub wait: bool,
@@ -51,6 +56,13 @@ impl UpdateMulticastGroupCliCommand {
5156
max_bandwidth: self.max_bandwidth,
5257
publisher_count: self.publisher_count,
5358
subscriber_count: self.subscriber_count,
59+
owner: self.owner.as_deref().map(|s| {
60+
if s.eq_ignore_ascii_case("me") {
61+
client.get_payer()
62+
} else {
63+
s.parse().unwrap()
64+
}
65+
}),
5466
})?;
5567
writeln!(out, "Signature: {signature}",)?;
5668

@@ -80,11 +92,35 @@ mod tests {
8092
use mockall::predicate;
8193
use solana_sdk::{pubkey::Pubkey, signature::Signature};
8294

95+
const SIGNATURE_BYTES: [u8; 64] = [
96+
120, 138, 162, 185, 59, 209, 241, 157, 71, 157, 74, 131, 4, 87, 54, 28, 38, 180, 222, 82,
97+
64, 62, 61, 62, 22, 46, 17, 203, 187, 136, 62, 43, 11, 38, 235, 17, 239, 82, 240, 139, 130,
98+
217, 227, 214, 9, 242, 141, 223, 94, 29, 184, 110, 62, 32, 87, 137, 63, 139, 100, 221, 20,
99+
137, 4, 5,
100+
];
101+
102+
const EXPECTED_SIGNATURE_STR: &str = "Signature: 3QnHBSdd4doEF6FgpLCejqEw42UQjfvNhQJwoYDSpoBszpCCqVft4cGoneDCnZ6Ez3ujzavzUu85u6F79WtLhcsv\n";
103+
104+
fn make_multicastgroup(pda_pubkey: Pubkey) -> MulticastGroup {
105+
MulticastGroup {
106+
account_type: AccountType::MulticastGroup,
107+
index: 1,
108+
bump_seed: 255,
109+
code: "test".to_string(),
110+
tenant_pk: Pubkey::new_unique(),
111+
multicast_ip: [10, 0, 0, 1].into(),
112+
max_bandwidth: 1000000000,
113+
status: MulticastGroupStatus::Activated,
114+
owner: pda_pubkey,
115+
publisher_count: 5,
116+
subscriber_count: 10,
117+
}
118+
}
119+
83120
#[test]
84121
fn test_cli_multicastgroup_update_bandwidth_parsing() {
85122
use clap::Parser;
86123

87-
// Define a test CLI structure to parse arguments
88124
#[derive(Parser, Debug)]
89125
struct TestCli {
90126
#[command(subcommand)]
@@ -96,14 +132,13 @@ mod tests {
96132
Update(UpdateMulticastGroupCliCommand),
97133
}
98134

99-
// Test various bandwidth formats
100135
let test_cases = vec![
101136
("1Gbps", 1_000_000_000u64),
102137
("100Mbps", 100_000_000u64),
103138
("500Kbps", 500_000u64),
104139
("1000bps", 1_000u64),
105-
("10gbps", 10_000_000_000u64), // lowercase
106-
("2.5Gbps", 2_500_000_000u64), // decimal
140+
("10gbps", 10_000_000_000u64),
141+
("2.5Gbps", 2_500_000_000u64),
107142
];
108143

109144
for (input, expected) in test_cases {
@@ -139,7 +174,6 @@ mod tests {
139174
}
140175
}
141176

142-
// Test invalid bandwidth formats
143177
let invalid_cases = vec!["invalid", "abc", "Gbps", ""];
144178

145179
for input in invalid_cases {
@@ -161,31 +195,68 @@ mod tests {
161195
}
162196
}
163197

198+
#[test]
199+
fn test_cli_multicastgroup_update_owner_parsing() {
200+
use clap::Parser;
201+
202+
#[derive(Parser, Debug)]
203+
struct TestCli {
204+
#[command(subcommand)]
205+
command: TestCommand,
206+
}
207+
208+
#[derive(clap::Subcommand, Debug)]
209+
enum TestCommand {
210+
Update(UpdateMulticastGroupCliCommand),
211+
}
212+
213+
let valid_pubkey = Pubkey::new_unique().to_string();
214+
215+
let valid_cases = vec!["me", valid_pubkey.as_str()];
216+
for input in valid_cases {
217+
let args = vec![
218+
"test",
219+
"update",
220+
"--pubkey",
221+
"test-pubkey",
222+
"--owner",
223+
input,
224+
];
225+
let result = TestCli::try_parse_from(args);
226+
assert!(
227+
result.is_ok(),
228+
"Should have accepted owner '{}': {:?}",
229+
input,
230+
result.err()
231+
);
232+
}
233+
234+
let invalid_cases = vec!["not_a_pubkey", "invalid key!"];
235+
for input in invalid_cases {
236+
let args = vec![
237+
"test",
238+
"update",
239+
"--pubkey",
240+
"test-pubkey",
241+
"--owner",
242+
input,
243+
];
244+
let result = TestCli::try_parse_from(args);
245+
assert!(
246+
result.is_err(),
247+
"Should have rejected invalid owner '{}'",
248+
input
249+
);
250+
}
251+
}
252+
164253
#[test]
165254
fn test_cli_multicastgroup_update() {
166255
let mut client = create_test_client();
167256

168257
let (pda_pubkey, _bump_seed) = get_multicastgroup_pda(&client.get_program_id(), 1);
169-
let signature = Signature::from([
170-
120, 138, 162, 185, 59, 209, 241, 157, 71, 157, 74, 131, 4, 87, 54, 28, 38, 180, 222,
171-
82, 64, 62, 61, 62, 22, 46, 17, 203, 187, 136, 62, 43, 11, 38, 235, 17, 239, 82, 240,
172-
139, 130, 217, 227, 214, 9, 242, 141, 223, 94, 29, 184, 110, 62, 32, 87, 137, 63, 139,
173-
100, 221, 20, 137, 4, 5,
174-
]);
175-
176-
let multicastgroup = MulticastGroup {
177-
account_type: AccountType::MulticastGroup,
178-
index: 1,
179-
bump_seed: 255,
180-
code: "test".to_string(),
181-
tenant_pk: Pubkey::new_unique(),
182-
multicast_ip: [10, 0, 0, 1].into(),
183-
max_bandwidth: 1000000000,
184-
status: MulticastGroupStatus::Activated,
185-
owner: pda_pubkey,
186-
publisher_count: 5,
187-
subscriber_count: 10,
188-
};
258+
let signature = Signature::from(SIGNATURE_BYTES);
259+
let multicastgroup = make_multicastgroup(pda_pubkey);
189260

190261
client
191262
.expect_check_requirements()
@@ -206,10 +277,10 @@ mod tests {
206277
max_bandwidth: Some(1000000000),
207278
publisher_count: Some(5),
208279
subscriber_count: Some(10),
280+
owner: None,
209281
}))
210282
.returning(move |_| Ok(signature));
211283

212-
/*****************************************************************************************************/
213284
let mut output = Vec::new();
214285
let res = UpdateMulticastGroupCliCommand {
215286
pubkey: pda_pubkey.to_string(),
@@ -218,13 +289,108 @@ mod tests {
218289
max_bandwidth: Some(1000000000),
219290
publisher_count: Some(5),
220291
subscriber_count: Some(10),
292+
owner: None,
293+
wait: false,
294+
}
295+
.execute(&client, &mut output);
296+
assert!(res.is_ok());
297+
assert_eq!(String::from_utf8(output).unwrap(), EXPECTED_SIGNATURE_STR);
298+
}
299+
300+
#[test]
301+
fn test_cli_multicastgroup_update_with_explicit_owner() {
302+
let mut client = create_test_client();
303+
304+
let (pda_pubkey, _bump_seed) = get_multicastgroup_pda(&client.get_program_id(), 1);
305+
let explicit_owner = Pubkey::new_unique();
306+
let signature = Signature::from(SIGNATURE_BYTES);
307+
let multicastgroup = make_multicastgroup(pda_pubkey);
308+
309+
client
310+
.expect_check_requirements()
311+
.with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE))
312+
.returning(|_| Ok(()));
313+
client
314+
.expect_get_multicastgroup()
315+
.with(predicate::eq(GetMulticastGroupCommand {
316+
pubkey_or_code: pda_pubkey.to_string(),
317+
}))
318+
.returning(move |_| Ok((pda_pubkey, multicastgroup.clone())));
319+
client
320+
.expect_update_multicastgroup()
321+
.with(predicate::eq(UpdateMulticastGroupCommand {
322+
pubkey: pda_pubkey,
323+
code: None,
324+
multicast_ip: None,
325+
max_bandwidth: None,
326+
publisher_count: None,
327+
subscriber_count: None,
328+
owner: Some(explicit_owner),
329+
}))
330+
.returning(move |_| Ok(signature));
331+
332+
let mut output = Vec::new();
333+
let res = UpdateMulticastGroupCliCommand {
334+
pubkey: pda_pubkey.to_string(),
335+
code: None,
336+
multicast_ip: None,
337+
max_bandwidth: None,
338+
publisher_count: None,
339+
subscriber_count: None,
340+
owner: Some(explicit_owner.to_string()),
341+
wait: false,
342+
}
343+
.execute(&client, &mut output);
344+
assert!(res.is_ok());
345+
assert_eq!(String::from_utf8(output).unwrap(), EXPECTED_SIGNATURE_STR);
346+
}
347+
348+
#[test]
349+
fn test_cli_multicastgroup_update_owner_me() {
350+
let mut client = create_test_client();
351+
352+
let (pda_pubkey, _bump_seed) = get_multicastgroup_pda(&client.get_program_id(), 1);
353+
// The payer configured in create_test_client()
354+
let payer = Pubkey::from_str_const("DDddB7bhR9azxLAUEH7ZVtW168wRdreiDKhi4McDfKZt");
355+
let signature = Signature::from(SIGNATURE_BYTES);
356+
let multicastgroup = make_multicastgroup(pda_pubkey);
357+
358+
client
359+
.expect_check_requirements()
360+
.with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE))
361+
.returning(|_| Ok(()));
362+
client
363+
.expect_get_multicastgroup()
364+
.with(predicate::eq(GetMulticastGroupCommand {
365+
pubkey_or_code: pda_pubkey.to_string(),
366+
}))
367+
.returning(move |_| Ok((pda_pubkey, multicastgroup.clone())));
368+
client
369+
.expect_update_multicastgroup()
370+
.with(predicate::eq(UpdateMulticastGroupCommand {
371+
pubkey: pda_pubkey,
372+
code: None,
373+
multicast_ip: None,
374+
max_bandwidth: None,
375+
publisher_count: None,
376+
subscriber_count: None,
377+
owner: Some(payer),
378+
}))
379+
.returning(move |_| Ok(signature));
380+
381+
let mut output = Vec::new();
382+
let res = UpdateMulticastGroupCliCommand {
383+
pubkey: pda_pubkey.to_string(),
384+
code: None,
385+
multicast_ip: None,
386+
max_bandwidth: None,
387+
publisher_count: None,
388+
subscriber_count: None,
389+
owner: Some("me".to_string()),
221390
wait: false,
222391
}
223392
.execute(&client, &mut output);
224393
assert!(res.is_ok());
225-
let output_str = String::from_utf8(output).unwrap();
226-
assert_eq!(
227-
output_str,"Signature: 3QnHBSdd4doEF6FgpLCejqEw42UQjfvNhQJwoYDSpoBszpCCqVft4cGoneDCnZ6Ez3ujzavzUu85u6F79WtLhcsv\n"
228-
);
394+
assert_eq!(String::from_utf8(output).unwrap(), EXPECTED_SIGNATURE_STR);
229395
}
230396
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ mod tests {
994994
publisher_count: None,
995995
subscriber_count: None,
996996
use_onchain_allocation: false,
997+
owner: None,
997998
}),
998999
"UpdateMulticastGroup",
9991000
);

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ pub struct MulticastGroupUpdateArgs {
3737
/// Requires ResourceExtension account (MulticastGroupBlock).
3838
#[incremental(default = false)]
3939
pub use_onchain_allocation: bool,
40+
pub owner: Option<Pubkey>,
4041
}
4142

4243
impl fmt::Debug for MulticastGroupUpdateArgs {
4344
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4445
write!(
4546
f,
46-
"code: {:?}, multicast_ip: {:?}, max_bandwidth: {:?}, publisher_count: {:?}, subscriber_count: {:?}, use_onchain_allocation: {}",
47-
self.code, self.multicast_ip, self.max_bandwidth, self.publisher_count, self.subscriber_count, self.use_onchain_allocation
47+
"code: {:?}, multicast_ip: {:?}, max_bandwidth: {:?}, publisher_count: {:?}, subscriber_count: {:?}, use_onchain_allocation: {}, owner: {:?}",
48+
self.code, self.multicast_ip, self.max_bandwidth, self.publisher_count, self.subscriber_count, self.use_onchain_allocation, self.owner
4849
)
4950
}
5051
}
@@ -152,6 +153,9 @@ pub fn process_update_multicastgroup(
152153
if let Some(ref subscriber_count) = value.subscriber_count {
153154
multicastgroup.subscriber_count = *subscriber_count;
154155
}
156+
if let Some(ref owner) = value.owner {
157+
multicastgroup.owner = *owner;
158+
}
155159

156160
try_acc_write(
157161
&multicastgroup,

smartcontract/programs/doublezero-serviceability/tests/multicastgroup_onchain_allocation_test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ async fn test_update_multicastgroup_with_onchain_reallocation() {
410410
publisher_count: None,
411411
subscriber_count: None,
412412
use_onchain_allocation: true,
413+
owner: None,
413414
}),
414415
vec![
415416
AccountMeta::new(mgroup_pubkey, false),
@@ -490,6 +491,7 @@ async fn test_update_multicastgroup_backward_compat() {
490491
publisher_count: None,
491492
subscriber_count: None,
492493
use_onchain_allocation: false,
494+
owner: None,
493495
}),
494496
vec![
495497
AccountMeta::new(mgroup_pubkey, false),
@@ -557,6 +559,7 @@ async fn test_update_multicastgroup_feature_flag_disabled() {
557559
publisher_count: None,
558560
subscriber_count: None,
559561
use_onchain_allocation: true,
562+
owner: None,
560563
}),
561564
vec![
562565
AccountMeta::new(mgroup_pubkey, false),

0 commit comments

Comments
 (0)