Skip to content

Commit d0c573c

Browse files
smartcontract: add changelog entry for mgroup allowlist PDAs
1 parent 24182de commit d0c573c

5 files changed

Lines changed: 108 additions & 183 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ All notable changes to this project will be documented in this file.
2727
- Record successful GetConfig gRPC calls to ClickHouse for device telemetry tracking
2828
- Onchain programs
2929
- Enforce that `CloseAccessPass` only closes AccessPass accounts when `connection_count == 0`, preventing closure while active connections are present.
30+
- Move multicast group allowlists from unbounded vecs in AccessPass to dedicated `MGroupAllowlistEntry` PDAs, with self-migration on subscribe
3031
- Monitor
3132
- Add sol-balance watcher to track SOL balances for configured accounts and export Prometheus metrics for alerting
3233
- E2E tests

smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/add.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ pub fn process_add_multicastgroup_pub_allowlist(
8484
return Err(DoubleZeroError::NotAllowed.into());
8585
}
8686

87-
// Create AccessPass if it doesn't exist (with empty allowlist vecs)
8887
if accesspass_account.data_is_empty() {
8988
let (expected_pda_account, bump_seed) =
9089
get_accesspass_pda(program_id, &value.client_ip, &value.user_payer);
@@ -128,7 +127,6 @@ pub fn process_add_multicastgroup_pub_allowlist(
128127
);
129128
}
130129

131-
// Create the MGroupAllowlistEntry PDA (skip if already exists)
132130
if mgroup_al_entry_account.data_is_empty() {
133131
let (expected_pda, bump_seed) = get_mgroup_allowlist_entry_pda(
134132
program_id,

smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/add.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ pub fn process_add_multicastgroup_sub_allowlist(
8585
return Err(DoubleZeroError::NotAllowed.into());
8686
}
8787

88-
// Create AccessPass if it doesn't exist (with empty allowlist vecs)
8988
if accesspass_account.data_is_empty() {
9089
let (expected_pda_account, bump_seed) =
9190
get_accesspass_pda(program_id, &value.client_ip, &value.user_payer);
@@ -129,7 +128,6 @@ pub fn process_add_multicastgroup_sub_allowlist(
129128
);
130129
}
131130

132-
// Create the MGroupAllowlistEntry PDA (skip if already exists)
133131
if mgroup_al_entry_account.data_is_empty() {
134132
let (expected_pda, bump_seed) = get_mgroup_allowlist_entry_pda(
135133
program_id,

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

Lines changed: 81 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,65 @@ use solana_program::{
2222
pubkey::Pubkey,
2323
};
2424
use std::{fmt, net::Ipv4Addr};
25+
26+
/// Check if an access pass is allowed for a multicast group (PDA-first with Vec fallback + self-migration).
27+
/// Returns true if the Vec was modified and the access pass needs to be written back.
28+
#[allow(clippy::too_many_arguments)]
29+
pub(crate) fn check_and_migrate_allowlist<'a>(
30+
program_id: &Pubkey,
31+
accesspass_account: &AccountInfo<'a>,
32+
mgroup_account: &AccountInfo<'a>,
33+
al_entry_account: &AccountInfo<'a>,
34+
payer_account: &AccountInfo<'a>,
35+
system_program: &AccountInfo<'a>,
36+
allowlist_type: MGroupAllowlistType,
37+
allowlist_vec: &mut Vec<Pubkey>,
38+
) -> Result<bool, solana_program::program_error::ProgramError> {
39+
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
40+
program_id,
41+
accesspass_account.key,
42+
mgroup_account.key,
43+
allowlist_type as u8,
44+
);
45+
if is_valid_mgroup_allowlist_entry(al_entry_account, &expected_pda, program_id) {
46+
// PDA exists -> allowed (fast path)
47+
Ok(false)
48+
} else if allowlist_vec.contains(mgroup_account.key) {
49+
// Found in Vec -> self-migrate: create PDA + swap_remove from Vec
50+
assert_eq!(
51+
al_entry_account.key, &expected_pda,
52+
"Invalid MGroupAllowlistEntry PDA for {:?}",
53+
allowlist_type
54+
);
55+
let al_entry = MGroupAllowlistEntry {
56+
account_type: AccountType::MGroupAllowlistEntry,
57+
bump_seed: bump,
58+
allowlist_type,
59+
};
60+
try_acc_create(
61+
&al_entry,
62+
al_entry_account,
63+
payer_account,
64+
system_program,
65+
program_id,
66+
&[
67+
SEED_PREFIX,
68+
SEED_MGROUP_ALLOWLIST,
69+
&accesspass_account.key.to_bytes(),
70+
&mgroup_account.key.to_bytes(),
71+
&[allowlist_type as u8],
72+
&[bump],
73+
],
74+
)?;
75+
if let Some(pos) = allowlist_vec.iter().position(|k| k == mgroup_account.key) {
76+
allowlist_vec.swap_remove(pos);
77+
}
78+
Ok(true)
79+
} else {
80+
Err(DoubleZeroError::NotAllowed.into())
81+
}
82+
}
83+
2584
#[derive(BorshSerialize, BorshDeserializeIncremental, PartialEq, Clone)]
2685
pub struct MulticastGroupSubscribeArgs {
2786
#[incremental(default = Ipv4Addr::UNSPECIFIED)]
@@ -126,100 +185,34 @@ pub fn process_subscribe_multicastgroup(
126185
// Check if the user is in the allowlist (PDA-first with Vec fallback + self-migration)
127186
let mut accesspass_modified = false;
128187
if value.publisher {
129-
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
188+
accesspass_modified |= check_and_migrate_allowlist(
130189
program_id,
131-
accesspass_account.key,
132-
mgroup_account.key,
133-
MGroupAllowlistType::Publisher as u8,
134-
);
135-
if is_valid_mgroup_allowlist_entry(mgroup_pub_al_entry, &expected_pda, program_id) {
136-
// PDA exists -> allowed (fast path)
137-
} else if accesspass.mgroup_pub_allowlist.contains(mgroup_account.key) {
138-
// Found in Vec -> self-migrate: create PDA + swap_remove from Vec
139-
assert_eq!(
140-
mgroup_pub_al_entry.key, &expected_pda,
141-
"Invalid MGroupAllowlistEntry PDA for publisher"
142-
);
143-
let al_entry = MGroupAllowlistEntry {
144-
account_type: AccountType::MGroupAllowlistEntry,
145-
bump_seed: bump,
146-
allowlist_type: MGroupAllowlistType::Publisher,
147-
};
148-
try_acc_create(
149-
&al_entry,
150-
mgroup_pub_al_entry,
151-
payer_account,
152-
system_program,
153-
program_id,
154-
&[
155-
SEED_PREFIX,
156-
SEED_MGROUP_ALLOWLIST,
157-
&accesspass_account.key.to_bytes(),
158-
&mgroup_account.key.to_bytes(),
159-
&[MGroupAllowlistType::Publisher as u8],
160-
&[bump],
161-
],
162-
)?;
163-
if let Some(pos) = accesspass
164-
.mgroup_pub_allowlist
165-
.iter()
166-
.position(|k| k == mgroup_account.key)
167-
{
168-
accesspass.mgroup_pub_allowlist.swap_remove(pos);
169-
accesspass_modified = true;
170-
}
171-
} else {
190+
accesspass_account,
191+
mgroup_account,
192+
mgroup_pub_al_entry,
193+
payer_account,
194+
system_program,
195+
MGroupAllowlistType::Publisher,
196+
&mut accesspass.mgroup_pub_allowlist,
197+
)
198+
.inspect_err(|_| {
172199
msg!("{:?}", accesspass);
173-
return Err(DoubleZeroError::NotAllowed.into());
174-
}
200+
})?;
175201
}
176202
if value.subscriber {
177-
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
203+
accesspass_modified |= check_and_migrate_allowlist(
178204
program_id,
179-
accesspass_account.key,
180-
mgroup_account.key,
181-
MGroupAllowlistType::Subscriber as u8,
182-
);
183-
if is_valid_mgroup_allowlist_entry(mgroup_sub_al_entry, &expected_pda, program_id) {
184-
// PDA exists -> allowed (fast path)
185-
} else if accesspass.mgroup_sub_allowlist.contains(mgroup_account.key) {
186-
// Found in Vec -> self-migrate: create PDA + swap_remove from Vec
187-
assert_eq!(
188-
mgroup_sub_al_entry.key, &expected_pda,
189-
"Invalid MGroupAllowlistEntry PDA for subscriber"
190-
);
191-
let al_entry = MGroupAllowlistEntry {
192-
account_type: AccountType::MGroupAllowlistEntry,
193-
bump_seed: bump,
194-
allowlist_type: MGroupAllowlistType::Subscriber,
195-
};
196-
try_acc_create(
197-
&al_entry,
198-
mgroup_sub_al_entry,
199-
payer_account,
200-
system_program,
201-
program_id,
202-
&[
203-
SEED_PREFIX,
204-
SEED_MGROUP_ALLOWLIST,
205-
&accesspass_account.key.to_bytes(),
206-
&mgroup_account.key.to_bytes(),
207-
&[MGroupAllowlistType::Subscriber as u8],
208-
&[bump],
209-
],
210-
)?;
211-
if let Some(pos) = accesspass
212-
.mgroup_sub_allowlist
213-
.iter()
214-
.position(|k| k == mgroup_account.key)
215-
{
216-
accesspass.mgroup_sub_allowlist.swap_remove(pos);
217-
accesspass_modified = true;
218-
}
219-
} else {
205+
accesspass_account,
206+
mgroup_account,
207+
mgroup_sub_al_entry,
208+
payer_account,
209+
system_program,
210+
MGroupAllowlistType::Subscriber,
211+
&mut accesspass.mgroup_sub_allowlist,
212+
)
213+
.inspect_err(|_| {
220214
msg!("{:?}", accesspass);
221-
return Err(DoubleZeroError::NotAllowed.into());
222-
}
215+
})?;
223216
}
224217

225218
// Write back the accesspass if we migrated entries from Vec to PDA

smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs

Lines changed: 26 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
use crate::{
22
error::DoubleZeroError,
3-
pda::{get_accesspass_pda, get_mgroup_allowlist_entry_pda, get_user_old_pda, get_user_pda},
4-
seeds::{SEED_MGROUP_ALLOWLIST, SEED_PREFIX, SEED_USER},
3+
pda::{get_accesspass_pda, get_user_old_pda, get_user_pda},
4+
processors::multicastgroup::subscribe::check_and_migrate_allowlist,
5+
seeds::{SEED_PREFIX, SEED_USER},
56
serializer::{try_acc_create, try_acc_write},
67
state::{
78
accesspass::{AccessPass, AccessPassStatus, AccessPassType},
89
accounttype::AccountType,
910
device::{Device, DeviceStatus},
1011
globalstate::GlobalState,
11-
mgroup_allowlist_entry::{
12-
is_valid_mgroup_allowlist_entry, MGroupAllowlistEntry, MGroupAllowlistType,
13-
},
12+
mgroup_allowlist_entry::MGroupAllowlistType,
1413
multicastgroup::{MulticastGroup, MulticastGroupStatus},
1514
user::*,
1615
},
@@ -188,98 +187,34 @@ pub fn process_create_subscribe_user(
188187

189188
// Check if the user is in the allowlist (PDA-first with Vec fallback + self-migration)
190189
if value.publisher {
191-
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
190+
check_and_migrate_allowlist(
192191
program_id,
193-
accesspass_account.key,
194-
mgroup_account.key,
195-
MGroupAllowlistType::Publisher as u8,
196-
);
197-
if is_valid_mgroup_allowlist_entry(mgroup_pub_al_entry, &expected_pda, program_id) {
198-
// PDA exists -> allowed (fast path)
199-
} else if accesspass.mgroup_pub_allowlist.contains(mgroup_account.key) {
200-
// Found in Vec -> self-migrate: create PDA + swap_remove from Vec
201-
assert_eq!(
202-
mgroup_pub_al_entry.key, &expected_pda,
203-
"Invalid MGroupAllowlistEntry PDA for publisher"
204-
);
205-
let al_entry = MGroupAllowlistEntry {
206-
account_type: AccountType::MGroupAllowlistEntry,
207-
bump_seed: bump,
208-
allowlist_type: MGroupAllowlistType::Publisher,
209-
};
210-
try_acc_create(
211-
&al_entry,
212-
mgroup_pub_al_entry,
213-
payer_account,
214-
system_program,
215-
program_id,
216-
&[
217-
SEED_PREFIX,
218-
SEED_MGROUP_ALLOWLIST,
219-
&accesspass_account.key.to_bytes(),
220-
&mgroup_account.key.to_bytes(),
221-
&[MGroupAllowlistType::Publisher as u8],
222-
&[bump],
223-
],
224-
)?;
225-
if let Some(pos) = accesspass
226-
.mgroup_pub_allowlist
227-
.iter()
228-
.position(|k| k == mgroup_account.key)
229-
{
230-
accesspass.mgroup_pub_allowlist.swap_remove(pos);
231-
}
232-
} else {
192+
accesspass_account,
193+
mgroup_account,
194+
mgroup_pub_al_entry,
195+
payer_account,
196+
system_program,
197+
MGroupAllowlistType::Publisher,
198+
&mut accesspass.mgroup_pub_allowlist,
199+
)
200+
.inspect_err(|_| {
233201
msg!("{} -> {:?}", accesspass_account.key, accesspass);
234-
return Err(DoubleZeroError::NotAllowed.into());
235-
}
202+
})?;
236203
}
237204
if value.subscriber {
238-
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
205+
check_and_migrate_allowlist(
239206
program_id,
240-
accesspass_account.key,
241-
mgroup_account.key,
242-
MGroupAllowlistType::Subscriber as u8,
243-
);
244-
if is_valid_mgroup_allowlist_entry(mgroup_sub_al_entry, &expected_pda, program_id) {
245-
// PDA exists -> allowed (fast path)
246-
} else if accesspass.mgroup_sub_allowlist.contains(mgroup_account.key) {
247-
// Found in Vec -> self-migrate: create PDA + swap_remove from Vec
248-
assert_eq!(
249-
mgroup_sub_al_entry.key, &expected_pda,
250-
"Invalid MGroupAllowlistEntry PDA for subscriber"
251-
);
252-
let al_entry = MGroupAllowlistEntry {
253-
account_type: AccountType::MGroupAllowlistEntry,
254-
bump_seed: bump,
255-
allowlist_type: MGroupAllowlistType::Subscriber,
256-
};
257-
try_acc_create(
258-
&al_entry,
259-
mgroup_sub_al_entry,
260-
payer_account,
261-
system_program,
262-
program_id,
263-
&[
264-
SEED_PREFIX,
265-
SEED_MGROUP_ALLOWLIST,
266-
&accesspass_account.key.to_bytes(),
267-
&mgroup_account.key.to_bytes(),
268-
&[MGroupAllowlistType::Subscriber as u8],
269-
&[bump],
270-
],
271-
)?;
272-
if let Some(pos) = accesspass
273-
.mgroup_sub_allowlist
274-
.iter()
275-
.position(|k| k == mgroup_account.key)
276-
{
277-
accesspass.mgroup_sub_allowlist.swap_remove(pos);
278-
}
279-
} else {
207+
accesspass_account,
208+
mgroup_account,
209+
mgroup_sub_al_entry,
210+
payer_account,
211+
system_program,
212+
MGroupAllowlistType::Subscriber,
213+
&mut accesspass.mgroup_sub_allowlist,
214+
)
215+
.inspect_err(|_| {
280216
msg!("{} -> {:?}", accesspass_account.key, accesspass);
281-
return Err(DoubleZeroError::NotAllowed.into());
282-
}
217+
})?;
283218
}
284219

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

0 commit comments

Comments
 (0)