Skip to content

Commit 48a5b44

Browse files
smartcontract: skip tenant_allowlist check for multicast users
Multicast connections are not tenant-scoped. CreateSubscribeUser always passes tenant_account=None, so enforcing the access-pass tenant_allowlist caused multicast connections to fail when the access-pass was scoped to a specific tenant (e.g. Solana). Add regression test covering the case.
1 parent 5504a1f commit 48a5b44

2 files changed

Lines changed: 162 additions & 20 deletions

File tree

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -166,29 +166,31 @@ pub fn create_user_core(
166166
return Err(DoubleZeroError::Unauthorized.into());
167167
}
168168

169-
// Enforce tenant_allowlist: if access-pass has a non-default tenant in its
170-
// allowlist, the user's tenant must be in that list.
171-
if accesspass
172-
.tenant_allowlist
173-
.iter()
174-
.any(|pk| *pk != Pubkey::default())
175-
{
176-
let user_tenant_pk = core.tenant_account.map(|a| *a.key).unwrap_or_default();
177-
if !accesspass.tenant_allowlist.contains(&user_tenant_pk) {
169+
// Enforce tenant_allowlist for unicast users only. Multicast connections are not
170+
// tenant-scoped, so the access-pass tenant_allowlist does not apply to them.
171+
if user_type != UserType::Multicast {
172+
if accesspass
173+
.tenant_allowlist
174+
.iter()
175+
.any(|pk| *pk != Pubkey::default())
176+
{
177+
let user_tenant_pk = core.tenant_account.map(|a| *a.key).unwrap_or_default();
178+
if !accesspass.tenant_allowlist.contains(&user_tenant_pk) {
179+
msg!(
180+
"Tenant {} not in access-pass tenant_allowlist {:?}",
181+
user_tenant_pk,
182+
accesspass.tenant_allowlist
183+
);
184+
return Err(DoubleZeroError::TenantNotInAccessPassAllowlist.into());
185+
}
186+
} else if let Some(tenant_account) = core.tenant_account {
187+
let tenant = Tenant::try_from(tenant_account)?;
178188
msg!(
179-
"Tenant {} not in access-pass tenant_allowlist {:?}",
180-
user_tenant_pk,
181-
accesspass.tenant_allowlist
189+
"Access-pass has no tenant_allowlist, but user creation specifies tenant {}",
190+
tenant.code
182191
);
183192
return Err(DoubleZeroError::TenantNotInAccessPassAllowlist.into());
184193
}
185-
} else if let Some(tenant_account) = core.tenant_account {
186-
let tenant = Tenant::try_from(tenant_account)?;
187-
msg!(
188-
"Access-pass has no tenant_allowlist, but user creation specifies tenant {}",
189-
tenant.code
190-
);
191-
return Err(DoubleZeroError::TenantNotInAccessPassAllowlist.into());
192194
}
193195

194196
// Check Initial epoch

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

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use doublezero_serviceability::{
1414
pda::{
1515
get_accesspass_pda, get_contributor_pda, get_device_pda, get_exchange_pda,
1616
get_globalconfig_pda, get_globalstate_pda, get_location_pda, get_multicastgroup_pda,
17-
get_program_config_pda, get_resource_extension_pda, get_user_pda,
17+
get_program_config_pda, get_resource_extension_pda, get_tenant_pda, get_user_pda,
1818
},
1919
processors::{
2020
accesspass::set::SetAccessPassArgs,
@@ -34,6 +34,7 @@ use doublezero_serviceability::{
3434
},
3535
create::MulticastGroupCreateArgs,
3636
},
37+
tenant::create::TenantCreateArgs,
3738
user::create_subscribe::UserCreateSubscribeArgs,
3839
},
3940
resource::ResourceType,
@@ -932,3 +933,142 @@ async fn test_create_subscribe_user_inactive_mgroup_fails() {
932933
"Should fail with graceful error when mgroup is not activated"
933934
);
934935
}
936+
937+
/// Multicast user creation succeeds when the access-pass has a tenant_allowlist.
938+
///
939+
/// Regression test: multicast connections are not tenant-scoped. A user with an access-pass
940+
/// restricted to a specific tenant should still be able to create a multicast connection,
941+
/// because CreateSubscribeUser never passes a tenant account.
942+
#[tokio::test]
943+
async fn test_create_subscribe_user_ignores_tenant_allowlist() {
944+
let client_ip = [100, 0, 0, 8];
945+
let f = setup_create_subscribe_fixture(client_ip).await;
946+
let CreateSubscribeFixture {
947+
mut banks_client,
948+
payer,
949+
program_id,
950+
globalstate_pubkey,
951+
device_pubkey,
952+
mgroup_pubkey,
953+
user_ip,
954+
..
955+
} = f;
956+
957+
let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap();
958+
959+
// Create a tenant
960+
let (vrf_ids_pda, _, _) = get_resource_extension_pda(&program_id, ResourceType::VrfIds);
961+
let (tenant_pubkey, _) = get_tenant_pda(&program_id, "solana");
962+
execute_transaction(
963+
&mut banks_client,
964+
recent_blockhash,
965+
program_id,
966+
DoubleZeroInstruction::CreateTenant(TenantCreateArgs {
967+
code: "solana".to_string(),
968+
administrator: payer.pubkey(),
969+
token_account: None,
970+
metro_routing: true,
971+
route_liveness: false,
972+
}),
973+
vec![
974+
AccountMeta::new(tenant_pubkey, false),
975+
AccountMeta::new(globalstate_pubkey, false),
976+
AccountMeta::new(vrf_ids_pda, false),
977+
],
978+
&payer,
979+
)
980+
.await;
981+
982+
// Create access pass with the tenant in its allowlist
983+
let (accesspass_pubkey, _) = get_accesspass_pda(&program_id, &user_ip, &payer.pubkey());
984+
execute_transaction(
985+
&mut banks_client,
986+
recent_blockhash,
987+
program_id,
988+
DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs {
989+
accesspass_type: AccessPassType::Prepaid,
990+
client_ip: user_ip,
991+
last_access_epoch: 9999,
992+
allow_multiple_ip: false,
993+
}),
994+
vec![
995+
AccountMeta::new(accesspass_pubkey, false),
996+
AccountMeta::new(globalstate_pubkey, false),
997+
AccountMeta::new(payer.pubkey(), false),
998+
AccountMeta::new(Pubkey::default(), false), // no tenant to remove
999+
AccountMeta::new(tenant_pubkey, false), // add tenant to allowlist
1000+
],
1001+
&payer,
1002+
)
1003+
.await;
1004+
1005+
// Add mgroup to allowlists
1006+
execute_transaction(
1007+
&mut banks_client,
1008+
recent_blockhash,
1009+
program_id,
1010+
DoubleZeroInstruction::AddMulticastGroupPubAllowlist(AddMulticastGroupPubAllowlistArgs {
1011+
client_ip: user_ip,
1012+
user_payer: payer.pubkey(),
1013+
}),
1014+
vec![
1015+
AccountMeta::new(mgroup_pubkey, false),
1016+
AccountMeta::new(accesspass_pubkey, false),
1017+
AccountMeta::new(globalstate_pubkey, false),
1018+
],
1019+
&payer,
1020+
)
1021+
.await;
1022+
1023+
execute_transaction(
1024+
&mut banks_client,
1025+
recent_blockhash,
1026+
program_id,
1027+
DoubleZeroInstruction::AddMulticastGroupSubAllowlist(AddMulticastGroupSubAllowlistArgs {
1028+
client_ip: user_ip,
1029+
user_payer: payer.pubkey(),
1030+
}),
1031+
vec![
1032+
AccountMeta::new(mgroup_pubkey, false),
1033+
AccountMeta::new(accesspass_pubkey, false),
1034+
AccountMeta::new(globalstate_pubkey, false),
1035+
],
1036+
&payer,
1037+
)
1038+
.await;
1039+
1040+
// Multicast user creation should succeed even though access-pass has a tenant_allowlist
1041+
let (user_pubkey, _) = get_user_pda(&program_id, &user_ip, UserType::Multicast);
1042+
let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap();
1043+
execute_transaction(
1044+
&mut banks_client,
1045+
recent_blockhash,
1046+
program_id,
1047+
DoubleZeroInstruction::CreateSubscribeUser(UserCreateSubscribeArgs {
1048+
user_type: UserType::Multicast,
1049+
cyoa_type: UserCYOA::GREOverDIA,
1050+
client_ip: user_ip,
1051+
publisher: false,
1052+
subscriber: true,
1053+
tunnel_endpoint: Ipv4Addr::UNSPECIFIED,
1054+
dz_prefix_count: 0,
1055+
}),
1056+
vec![
1057+
AccountMeta::new(user_pubkey, false),
1058+
AccountMeta::new(device_pubkey, false),
1059+
AccountMeta::new(mgroup_pubkey, false),
1060+
AccountMeta::new(accesspass_pubkey, false),
1061+
AccountMeta::new(globalstate_pubkey, false),
1062+
],
1063+
&payer,
1064+
)
1065+
.await;
1066+
1067+
let user = get_account_data(&mut banks_client, user_pubkey)
1068+
.await
1069+
.expect("User should exist")
1070+
.get_user()
1071+
.unwrap();
1072+
assert_eq!(user.status, UserStatus::Pending);
1073+
assert_eq!(user.subscribers, vec![mgroup_pubkey]);
1074+
}

0 commit comments

Comments
 (0)