Skip to content

Commit 71d5523

Browse files
smartcontract: add changelog entry for mgroup allowlist PDAs
1 parent 771c467 commit 71d5523

5 files changed

Lines changed: 111 additions & 183 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ All notable changes to this project will be documented in this file.
2424
- Record successful GetConfig gRPC calls to ClickHouse for device telemetry tracking
2525
- Onchain programs
2626
- Enforce that `CloseAccessPass` only closes AccessPass accounts when `connection_count == 0`, preventing closure while active connections are present.
27+
- Move multicast group allowlists from unbounded vecs in AccessPass to dedicated `MGroupAllowlistEntry` PDAs, with self-migration on subscribe
2728
- Monitor
2829
- Add sol-balance watcher to track SOL balances for configured accounts and export Prometheus metrics for alerting
2930
- 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: 82 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,64 @@ 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+
pub(crate) fn check_and_migrate_allowlist<'a>(
29+
program_id: &Pubkey,
30+
accesspass_account: &AccountInfo<'a>,
31+
mgroup_account: &AccountInfo<'a>,
32+
al_entry_account: &AccountInfo<'a>,
33+
payer_account: &AccountInfo<'a>,
34+
system_program: &AccountInfo<'a>,
35+
allowlist_type: MGroupAllowlistType,
36+
allowlist_vec: &mut Vec<Pubkey>,
37+
) -> Result<bool, solana_program::program_error::ProgramError> {
38+
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
39+
program_id,
40+
accesspass_account.key,
41+
mgroup_account.key,
42+
allowlist_type as u8,
43+
);
44+
if is_valid_mgroup_allowlist_entry(al_entry_account, &expected_pda, program_id) {
45+
// PDA exists -> allowed (fast path)
46+
Ok(false)
47+
} else if allowlist_vec.contains(mgroup_account.key) {
48+
// Found in Vec -> self-migrate: create PDA + swap_remove from Vec
49+
assert_eq!(
50+
al_entry_account.key, &expected_pda,
51+
"Invalid MGroupAllowlistEntry PDA for {:?}",
52+
allowlist_type
53+
);
54+
let al_entry = MGroupAllowlistEntry {
55+
account_type: AccountType::MGroupAllowlistEntry,
56+
bump_seed: bump,
57+
allowlist_type,
58+
};
59+
try_acc_create(
60+
&al_entry,
61+
al_entry_account,
62+
payer_account,
63+
system_program,
64+
program_id,
65+
&[
66+
SEED_PREFIX,
67+
SEED_MGROUP_ALLOWLIST,
68+
&accesspass_account.key.to_bytes(),
69+
&mgroup_account.key.to_bytes(),
70+
&[allowlist_type as u8],
71+
&[bump],
72+
],
73+
)?;
74+
if let Some(pos) = allowlist_vec.iter().position(|k| k == mgroup_account.key) {
75+
allowlist_vec.swap_remove(pos);
76+
}
77+
Ok(true)
78+
} else {
79+
Err(DoubleZeroError::NotAllowed.into())
80+
}
81+
}
82+
2583
#[derive(BorshSerialize, BorshDeserializeIncremental, PartialEq, Clone)]
2684
pub struct MulticastGroupSubscribeArgs {
2785
#[incremental(default = Ipv4Addr::UNSPECIFIED)]
@@ -126,100 +184,36 @@ pub fn process_subscribe_multicastgroup(
126184
// Check if the user is in the allowlist (PDA-first with Vec fallback + self-migration)
127185
let mut accesspass_modified = false;
128186
if value.publisher {
129-
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
187+
accesspass_modified |= check_and_migrate_allowlist(
130188
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 {
189+
accesspass_account,
190+
mgroup_account,
191+
mgroup_pub_al_entry,
192+
payer_account,
193+
system_program,
194+
MGroupAllowlistType::Publisher,
195+
&mut accesspass.mgroup_pub_allowlist,
196+
)
197+
.map_err(|e| {
172198
msg!("{:?}", accesspass);
173-
return Err(DoubleZeroError::NotAllowed.into());
174-
}
199+
e
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+
.map_err(|e| {
220214
msg!("{:?}", accesspass);
221-
return Err(DoubleZeroError::NotAllowed.into());
222-
}
215+
e
216+
})?;
223217
}
224218

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

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

Lines changed: 28 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,36 @@ 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+
.map_err(|e| {
233201
msg!("{} -> {:?}", accesspass_account.key, accesspass);
234-
return Err(DoubleZeroError::NotAllowed.into());
235-
}
202+
e
203+
})?;
236204
}
237205
if value.subscriber {
238-
let (expected_pda, bump) = get_mgroup_allowlist_entry_pda(
206+
check_and_migrate_allowlist(
239207
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 {
208+
accesspass_account,
209+
mgroup_account,
210+
mgroup_sub_al_entry,
211+
payer_account,
212+
system_program,
213+
MGroupAllowlistType::Subscriber,
214+
&mut accesspass.mgroup_sub_allowlist,
215+
)
216+
.map_err(|e| {
280217
msg!("{} -> {:?}", accesspass_account.key, accesspass);
281-
return Err(DoubleZeroError::NotAllowed.into());
282-
}
218+
e
219+
})?;
283220
}
284221

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

0 commit comments

Comments
 (0)