Skip to content

Commit 1d42f65

Browse files
smartcontract: integrate Index into multicast group lifecycle
Wire Index account management into multicast group create, update, delete, and close instructions. Update Rust SDK commands to derive and pass Index PDAs. Add close_index/rename_index args to control Index lifecycle during deactivation and updates. Update activator, Go/Python/TypeScript SDKs, and all integration tests.
1 parent 4a51bc6 commit 1d42f65

27 files changed

Lines changed: 812 additions & 142 deletions

File tree

activator/src/process/multicastgroup.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,22 @@ mod tests {
398398
// Insert it first so it can be removed
399399
multicastgroups.insert(pubkey, multicastgroup.clone());
400400

401-
// Stateless mode: use_onchain_deallocation=true
401+
// Mock get() for DeactivateMulticastGroupCommand which fetches the
402+
// multicast group to derive the Index PDA
403+
let mgroup_for_get = multicastgroup.clone();
404+
client
405+
.expect_get()
406+
.with(predicate::eq(pubkey))
407+
.returning(move |_| Ok(AccountData::MulticastGroup(mgroup_for_get.clone())));
408+
409+
// Stateless mode: use_onchain_deallocation=true, close_index=true
402410
client
403411
.expect_execute_transaction()
404412
.with(
405413
predicate::eq(DoubleZeroInstruction::DeactivateMulticastGroup(
406414
MulticastGroupDeactivateArgs {
407415
use_onchain_deallocation: true,
416+
close_index: true,
408417
},
409418
)),
410419
predicate::always(),

e2e/internal/qa/provisioning.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,4 +600,3 @@ func formatBandwidth(bps uint64) string {
600600
}
601601
return fmt.Sprintf("%d bps", bps)
602602
}
603-

sdk/serviceability/go/state.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
ResourceExtensionType AccountType = 12
2626
TenantType AccountType = 13
2727
PermissionType AccountType = 15
28+
IndexType AccountType = 16
2829
)
2930

3031
type LocationStatus uint8

sdk/serviceability/python/serviceability/state.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class AccountTypeEnum(IntEnum):
4242
ACCESS_PASS = 11
4343
TENANT = 13
4444
PERMISSION = 15
45+
INDEX = 16
4546

4647

4748
# ---------------------------------------------------------------------------

sdk/serviceability/typescript/serviceability/state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const ACCOUNT_TYPE_CONTRIBUTOR = 10;
3232
export const ACCOUNT_TYPE_ACCESS_PASS = 11;
3333
export const ACCOUNT_TYPE_TENANT = 13;
3434
export const ACCOUNT_TYPE_PERMISSION = 15;
35+
export const ACCOUNT_TYPE_INDEX = 16;
3536

3637
// ---------------------------------------------------------------------------
3738
// Enum string mappings

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ mod tests {
988988
publisher_count: None,
989989
subscriber_count: None,
990990
use_onchain_allocation: false,
991+
rename_index: false,
991992
}),
992993
"UpdateMulticastGroup",
993994
);
@@ -1005,13 +1006,15 @@ mod tests {
10051006
test_instruction(
10061007
DoubleZeroInstruction::DeleteMulticastGroup(MulticastGroupDeleteArgs {
10071008
use_onchain_deallocation: false,
1009+
close_index: false,
10081010
}),
10091011
"DeleteMulticastGroup",
10101012
);
10111013

10121014
test_instruction(
10131015
DoubleZeroInstruction::DeactivateMulticastGroup(MulticastGroupDeactivateArgs {
10141016
use_onchain_deallocation: false,
1017+
close_index: false,
10151018
}),
10161019
"DeactivateMulticastGroup",
10171020
);

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

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use crate::{
22
error::DoubleZeroError,
3-
pda::get_resource_extension_pda,
3+
pda::{get_index_pda, get_resource_extension_pda},
44
processors::resource::deallocate_ip,
55
resource::ResourceType,
6+
seeds::SEED_MULTICAST_GROUP,
67
serializer::try_acc_close,
7-
state::{globalstate::GlobalState, multicastgroup::*},
8+
state::{globalstate::GlobalState, index::Index, multicastgroup::*},
89
};
910
use borsh::BorshSerialize;
1011
use borsh_incremental::BorshDeserializeIncremental;
@@ -24,14 +25,17 @@ pub struct MulticastGroupDeactivateArgs {
2425
/// When false, legacy behavior is used (no deallocation).
2526
#[incremental(default = false)]
2627
pub use_onchain_deallocation: bool,
28+
/// When true, close the associated Index account alongside the multicast group.
29+
#[incremental(default = false)]
30+
pub close_index: bool,
2731
}
2832

2933
impl fmt::Debug for MulticastGroupDeactivateArgs {
3034
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3135
write!(
3236
f,
33-
"use_onchain_deallocation: {}",
34-
self.use_onchain_deallocation
37+
"use_onchain_deallocation: {}, close_index: {}",
38+
self.use_onchain_deallocation, self.close_index
3539
)
3640
}
3741
}
@@ -47,10 +51,12 @@ pub fn process_closeaccount_multicastgroup(
4751
let owner_account = next_account_info(accounts_iter)?;
4852
let globalstate_account = next_account_info(accounts_iter)?;
4953

50-
// Optional: ResourceExtension account for on-chain deallocation (before payer)
51-
// Account layout WITH ResourceExtension (use_onchain_deallocation = true):
52-
// [multicastgroup, owner, globalstate, multicast_group_block, payer, system]
53-
// Account layout WITHOUT (legacy, use_onchain_deallocation = false):
54+
// Optional accounts (before payer/system):
55+
// Account layout WITH deallocation + index:
56+
// [multicastgroup, owner, globalstate, multicast_group_block, index, payer, system]
57+
// Account layout WITHOUT deallocation, with index:
58+
// [multicastgroup, owner, globalstate, index, payer, system]
59+
// Legacy (no deallocation, no index):
5460
// [multicastgroup, owner, globalstate, payer, system]
5561
let resource_extension_account = if value.use_onchain_deallocation {
5662
let multicast_group_block_ext = next_account_info(accounts_iter)?;
@@ -59,6 +65,12 @@ pub fn process_closeaccount_multicastgroup(
5965
None
6066
};
6167

68+
let index_account = if value.close_index {
69+
Some(next_account_info(accounts_iter)?)
70+
} else {
71+
None
72+
};
73+
6274
let payer_account = next_account_info(accounts_iter)?;
6375
let system_program = next_account_info(accounts_iter)?;
6476

@@ -137,6 +149,24 @@ pub fn process_closeaccount_multicastgroup(
137149

138150
try_acc_close(multicastgroup_account, owner_account)?;
139151

152+
// Close the Index account if provided
153+
if let Some(index_acc) = index_account {
154+
assert_eq!(index_acc.owner, program_id, "Invalid Index Account Owner");
155+
assert!(index_acc.is_writable, "Index Account is not writable");
156+
157+
let (expected_index_pda, _) =
158+
get_index_pda(program_id, SEED_MULTICAST_GROUP, &multicastgroup.code);
159+
assert_eq!(index_acc.key, &expected_index_pda, "Invalid Index Pubkey");
160+
161+
let index = Index::try_from(index_acc)?;
162+
assert_eq!(
163+
index.pk, *multicastgroup_account.key,
164+
"Index does not point to this MulticastGroup"
165+
);
166+
167+
try_acc_close(index_acc, payer_account)?;
168+
}
169+
140170
#[cfg(test)]
141171
msg!("Deactivated: MulticastGroup closed");
142172

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use crate::{
22
error::DoubleZeroError,
3-
pda::{get_multicastgroup_pda, get_resource_extension_pda},
3+
pda::{get_index_pda, get_multicastgroup_pda, get_resource_extension_pda},
44
processors::{resource::allocate_ip, validation::validate_program_account},
55
resource::ResourceType,
6-
seeds::{SEED_MULTICAST_GROUP, SEED_PREFIX},
6+
seeds::{SEED_INDEX, SEED_MULTICAST_GROUP, SEED_PREFIX},
77
serializer::{try_acc_create, try_acc_write},
88
state::{
99
accounttype::AccountType,
1010
feature_flags::{is_feature_enabled, FeatureFlag},
1111
globalstate::GlobalState,
12+
index::Index,
1213
multicastgroup::*,
1314
},
1415
};
@@ -57,17 +58,18 @@ pub fn process_create_multicastgroup(
5758
let mgroup_account = next_account_info(accounts_iter)?;
5859
let globalstate_account = next_account_info(accounts_iter)?;
5960

60-
// Optional: ResourceExtension account for onchain allocation (before payer)
61+
// Optional: ResourceExtension account for onchain allocation
6162
// Account layout WITH ResourceExtension (use_onchain_allocation = true):
62-
// [mgroup, globalstate, multicast_group_block, payer, system]
63+
// [mgroup, globalstate, multicast_group_block, index, payer, system]
6364
// Account layout WITHOUT (legacy, use_onchain_allocation = false):
64-
// [mgroup, globalstate, payer, system]
65+
// [mgroup, globalstate, index, payer, system]
6566
let resource_extension_account = if value.use_onchain_allocation {
6667
Some(next_account_info(accounts_iter)?)
6768
} else {
6869
None
6970
};
7071

72+
let index_account = next_account_info(accounts_iter)?;
7173
let payer_account = next_account_info(accounts_iter)?;
7274
let system_program = next_account_info(accounts_iter)?;
7375

@@ -80,6 +82,7 @@ pub fn process_create_multicastgroup(
8082
// Validate and normalize code
8183
let code =
8284
validate_account_code(&value.code).map_err(|_| DoubleZeroError::InvalidAccountCode)?;
85+
let lowercase_code = code.to_ascii_lowercase();
8386

8487
// Check the owner of the accounts
8588
assert_eq!(
@@ -114,6 +117,10 @@ pub fn process_create_multicastgroup(
114117
return Err(ProgramError::AccountAlreadyInitialized);
115118
}
116119

120+
// Validate Index PDA (before code is moved into multicastgroup)
121+
let (expected_index_pda, index_bump_seed) =
122+
get_index_pda(program_id, SEED_MULTICAST_GROUP, &code);
123+
117124
let mut multicastgroup = MulticastGroup {
118125
account_type: AccountType::MulticastGroup,
119126
owner: value.owner,
@@ -147,6 +154,16 @@ pub fn process_create_multicastgroup(
147154
multicastgroup.multicast_ip = allocate_ip(multicast_group_block_ext, 1)?.ip();
148155
multicastgroup.status = MulticastGroupStatus::Activated;
149156
}
157+
assert_eq!(
158+
index_account.key, &expected_index_pda,
159+
"Invalid Index Pubkey"
160+
);
161+
assert!(index_account.is_writable, "Index Account is not writable");
162+
163+
// Uniqueness: index account must not already exist
164+
if !index_account.data_is_empty() {
165+
return Err(ProgramError::AccountAlreadyInitialized);
166+
}
150167

151168
try_acc_create(
152169
&multicastgroup,
@@ -161,6 +178,29 @@ pub fn process_create_multicastgroup(
161178
&[bump_seed],
162179
],
163180
)?;
181+
182+
// Create the Index account pointing to the multicast group
183+
let index = Index {
184+
account_type: AccountType::Index,
185+
pk: *mgroup_account.key,
186+
bump_seed: index_bump_seed,
187+
};
188+
189+
try_acc_create(
190+
&index,
191+
index_account,
192+
payer_account,
193+
system_program,
194+
program_id,
195+
&[
196+
SEED_PREFIX,
197+
SEED_INDEX,
198+
SEED_MULTICAST_GROUP,
199+
lowercase_code.as_bytes(),
200+
&[index_bump_seed],
201+
],
202+
)?;
203+
164204
try_acc_write(&globalstate, globalstate_account, payer_account, accounts)?;
165205

166206
Ok(())

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

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::{
22
error::DoubleZeroError,
3-
pda::get_resource_extension_pda,
3+
pda::{get_index_pda, get_resource_extension_pda},
44
processors::{resource::deallocate_ip, validation::validate_program_account},
55
resource::ResourceType,
6+
seeds::SEED_MULTICAST_GROUP,
67
serializer::{try_acc_close, try_acc_write},
78
state::{
89
feature_flags::{is_feature_enabled, FeatureFlag},
910
globalstate::GlobalState,
11+
index::Index,
1012
multicastgroup::*,
1113
},
1214
};
@@ -28,14 +30,17 @@ pub struct MulticastGroupDeleteArgs {
2830
/// Requires ResourceExtension accounts and owner account.
2931
#[incremental(default = false)]
3032
pub use_onchain_deallocation: bool,
33+
/// When true, close the associated Index account alongside the multicast group.
34+
#[incremental(default = false)]
35+
pub close_index: bool,
3136
}
3237

3338
impl fmt::Debug for MulticastGroupDeleteArgs {
3439
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3540
write!(
3641
f,
37-
"use_onchain_deallocation: {}",
38-
self.use_onchain_deallocation
42+
"use_onchain_deallocation: {}, close_index: {}",
43+
self.use_onchain_deallocation, self.close_index
3944
)
4045
}
4146
}
@@ -50,10 +55,12 @@ pub fn process_delete_multicastgroup(
5055
let multicastgroup_account = next_account_info(accounts_iter)?;
5156
let globalstate_account = next_account_info(accounts_iter)?;
5257

53-
// Optional: additional accounts for atomic deallocation (before payer)
54-
// Account layout WITH deallocation (use_onchain_deallocation = true):
55-
// [mgroup, globalstate, multicast_group_block, owner, payer, system]
56-
// Account layout WITHOUT (legacy, use_onchain_deallocation = false):
58+
// Optional: additional accounts for atomic deallocation
59+
// Account layout WITH deallocation + index:
60+
// [mgroup, globalstate, multicast_group_block, owner, index, payer, system]
61+
// Account layout WITHOUT deallocation, with index:
62+
// [mgroup, globalstate, index, payer, system]
63+
// Legacy (no deallocation, no index):
5764
// [mgroup, globalstate, payer, system]
5865
let deallocation_accounts = if value.use_onchain_deallocation {
5966
let multicast_group_block_ext = next_account_info(accounts_iter)?;
@@ -63,6 +70,12 @@ pub fn process_delete_multicastgroup(
6370
None
6471
};
6572

73+
let index_account = if value.close_index {
74+
Some(next_account_info(accounts_iter)?)
75+
} else {
76+
None
77+
};
78+
6679
let payer_account = next_account_info(accounts_iter)?;
6780
let system_program = next_account_info(accounts_iter)?;
6881

@@ -98,6 +111,7 @@ pub fn process_delete_multicastgroup(
98111
}
99112

100113
let multicastgroup: MulticastGroup = MulticastGroup::try_from(multicastgroup_account)?;
114+
let multicastgroup_code = multicastgroup.code.clone();
101115

102116
if matches!(multicastgroup.status, MulticastGroupStatus::Deleting) {
103117
return Err(DoubleZeroError::InvalidStatus.into());
@@ -158,5 +172,25 @@ pub fn process_delete_multicastgroup(
158172
msg!("Deleted: {:?}", multicastgroup_account);
159173
}
160174

175+
// Close the Index account if provided
176+
if let Some(index_acc) = index_account {
177+
assert_eq!(index_acc.owner, program_id, "Invalid Index Account Owner");
178+
assert!(index_acc.is_writable, "Index Account is not writable");
179+
180+
// Verify the Index PDA matches
181+
let (expected_index_pda, _) =
182+
get_index_pda(program_id, SEED_MULTICAST_GROUP, &multicastgroup_code);
183+
assert_eq!(index_acc.key, &expected_index_pda, "Invalid Index Pubkey");
184+
185+
// Verify it's an Index account pointing to this multicast group
186+
let index = Index::try_from(index_acc)?;
187+
assert_eq!(
188+
index.pk, *multicastgroup_account.key,
189+
"Index does not point to this MulticastGroup"
190+
);
191+
192+
try_acc_close(index_acc, payer_account)?;
193+
}
194+
161195
Ok(())
162196
}

0 commit comments

Comments
 (0)