From 8b03610128099e1333a1cb3ceef04fa3675a1bcc Mon Sep 17 00:00:00 2001 From: Juan Olveira Date: Sun, 7 Sep 2025 13:13:06 +0000 Subject: [PATCH 1/8] Refactor multicast group allowlist commands to include client IP and user payer - Removed unused fields from MulticastGroup struct and related deserialization logic. - Updated Add/Remove publisher/subscriber allowlist commands to accept client IP and user payer. - Deleted List commands for publisher and subscriber allowlists as they are no longer needed. - Adjusted tests to reflect changes in command structure and added necessary assertions. - Updated start-test.sh script to accommodate new command parameters for allowlist operations. - update multicast group PDA references in tests - remove unused globalstate PDA reference in multicast group subscribe tests - replace user allowlist with Access Pass in e2e test - update user_payer in sdk - update MulticastAllowListAdd to use --user-payer flag - improve error handling in deserialize_vec_with_capacity and add unit tests - update MulticastAllowListAdd to log user-payer instead of pubkey - update GetMulticastGroupCliCommand to filter users by pubkey and adjust mode text representation - add Publisher_count and Subscriber_count fields to MulticastGroup struct lint: refactor test setup for multicast group to improve readability chore: update goreleaser configuration for nightly releases chore: reorder git and nightly sections in goreleaser configuration changelog: reorganize sections and clarify changes in onchain programs Add support for multiple client IPs in MulticastAllowListAdd RPC Fix AccessPassNotFound error variant number and clean up unused code in helper functions Remove redundant client addition to user allowlist in device max users rollover test Enhance code clarity by improving comments and formatting in helper functions and multicast group display Refactor MulticastAllowListAdd to log client IP and streamline command execution Refactor MulticastGroup struct: rename Publisher_count and Subscriber_count to PublisherCount and SubscriberCount for consistency refactor: standardize formatting in MulticastGroup struct e2e/qa: client ip in MulticastAllowListAdd Fix import order and resolve merge conflict in agent.pb.go Refactor MulticastAllowListAddRequest by removing unused GetClientIp method --- CHANGELOG.md | 8 +- activator/src/process/multicastgroup.rs | 6 +- client/doublezero/src/cli/user.rs | 38 +---- client/doublezero/src/command/connect.rs | 14 +- client/doublezero/src/main.rs | 7 +- .../internal/controller/server_test.go | 16 -- controlplane/doublezero-admin/src/cli/user.rs | 38 +---- controlplane/doublezero-admin/src/main.rs | 7 +- e2e/device_maxusers_rollover_test.go | 17 +- e2e/device_stress_test.go | 8 - e2e/internal/rpc/agent.go | 63 +++++++- e2e/main_test.go | 12 +- e2e/multi_client_test.go | 10 +- e2e/no_ifaces_peers_test.go | 11 +- e2e/proto/qa/agent.proto | 1 + e2e/proto/qa/gen/pb-go/agent.pb.go | 5 +- e2e/qa_test.go | 18 ++- e2e/user_ban_test.go | 11 +- smartcontract/cli/src/accesspass/list.rs | 54 ++++++- smartcontract/cli/src/allowlist/mod.rs | 1 - smartcontract/cli/src/doublezerocommand.rs | 40 ----- .../multicastgroup/allowlist/publisher/add.rs | 22 ++- .../allowlist/publisher/list.rs | 153 +++++++++++++----- .../allowlist/publisher/remove.rs | 24 ++- .../allowlist/subscriber/add.rs | 24 ++- .../allowlist/subscriber/list.rs | 153 +++++++++++++----- .../allowlist/subscriber/remove.rs | 24 ++- .../cli/src/multicastgroup/delete.rs | 6 +- smartcontract/cli/src/multicastgroup/get.rs | 59 +++---- smartcontract/cli/src/multicastgroup/list.rs | 27 +--- .../cli/src/multicastgroup/update.rs | 18 ++- .../cli/src/user/create_subscribe.rs | 6 +- smartcontract/cli/src/user/get.rs | 47 +++++- smartcontract/cli/src/user/list.rs | 19 ++- smartcontract/cli/src/user/subscribe.rs | 56 +++++-- .../doublezero-serviceability/src/error.rs | 4 + .../doublezero-serviceability/src/helper.rs | 30 ++++ .../src/instructions.rs | 15 +- .../src/processors/accesspass/check_status.rs | 7 + .../src/processors/accesspass/close.rs | 8 + .../src/processors/accesspass/set.rs | 8 +- .../multicastgroup/allowlist/publisher/add.rs | 100 ++++++++++-- .../allowlist/publisher/remove.rs | 52 +++++- .../allowlist/subscriber/add.rs | 100 ++++++++++-- .../allowlist/subscriber/remove.rs | 52 +++++- .../src/processors/multicastgroup/create.rs | 6 +- .../processors/multicastgroup/subscribe.rs | 106 ++++++------ .../src/processors/multicastgroup/update.rs | 8 + .../src/processors/user/activate.rs | 3 + .../src/processors/user/check_access_pass.rs | 8 + .../src/processors/user/create.rs | 8 + .../src/processors/user/create_subscribe.rs | 21 ++- .../src/processors/user/delete.rs | 8 + .../src/processors/user/resume.rs | 3 + .../src/state/accesspass.rs | 13 ++ .../src/state/multicastgroup.rs | 127 ++++----------- ...multicastgroup_allowlist_publisher_test.rs | 85 +++++++--- ...multicastgroup_allowlist_subcriber_test.rs | 89 ++++++---- .../tests/multicastgroup_test.rs | 8 + .../sdk/go/serviceability/client_test.go | 4 - .../sdk/go/serviceability/deserialize.go | 4 - smartcontract/sdk/go/serviceability/state.go | 26 ++- .../sdk/rs/src/commands/allowlist/mod.rs | 1 - .../multicastgroup/allowlist/publisher/add.rs | 35 ++-- .../allowlist/publisher/list.rs | 121 -------------- .../multicastgroup/allowlist/publisher/mod.rs | 1 - .../allowlist/publisher/remove.rs | 39 +++-- .../allowlist/subscriber/add.rs | 39 +++-- .../allowlist/subscriber/list.rs | 121 -------------- .../allowlist/subscriber/mod.rs | 1 - .../allowlist/subscriber/remove.rs | 39 +++-- .../rs/src/commands/multicastgroup/delete.rs | 29 +--- .../src/commands/multicastgroup/subscribe.rs | 85 +++++++--- .../rs/src/commands/multicastgroup/update.rs | 8 + .../rs/src/commands/user/create_subscribe.rs | 6 - .../sdk/rs/src/commands/user/delete.rs | 1 + smartcontract/test/start-test.sh | 48 ++---- 77 files changed, 1428 insertions(+), 1072 deletions(-) delete mode 100644 smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/list.rs delete mode 100644 smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/list.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bbcc2bde..7b213011f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ All notable changes to this project will be documented in this file. - Activator - Reduce logging noise when processing snapshot events - Wrap main select handler in loop to avoid shutdown on branch error +- Onchain programs + - Remove user-level allowlist management from CLI and admin interfaces; manage multicast group allowlists through AccessPass. + - Add Validate trait for core types (AccessPass, Contributor, Interface, etc.) and enforce runtime checks before account operations. + - Internet telemetry - Add circuit label to metrics; create a new metric for missing circuit samples - Create a new metric that tracks how long it takes collector tasks to run @@ -93,7 +97,6 @@ All notable changes to this project will be documented in this file. ### Changes - Onchain programs - - Add Validate trait for core types (AccessPass, Contributor, Interface, etc.) and enforce runtime checks before account operations. - Expand DoubleZeroError with granular variants (invalid IPs, ASN, MTU, VLAN, etc.) and derive PartialEq for easier testing. - Rename Config account type to GlobalConfig for clarity and consistency. - Fix bug in user update that caused DZ IP to be 0.0.0.0 @@ -120,7 +123,8 @@ All notable changes to this project will be documented in this file. - Updated unit tests and e2e tests to validate the new initialization and activation flow. - Contributor Operations - Contributors must explicitly run device update to set a valid max_users and activate a Device. - +- CLI + - Display multicast group memberships (publisher/subscriber) in AccessPass listings to improve visibility. ## [v0.6.2](https://github.com/malbeclabs/doublezero/compare/client/v0.6.0...client/v0.6.2) – 2025-09-02 diff --git a/activator/src/process/multicastgroup.rs b/activator/src/process/multicastgroup.rs index 18c6c7fa3..a6b741fec 100644 --- a/activator/src/process/multicastgroup.rs +++ b/activator/src/process/multicastgroup.rs @@ -156,13 +156,11 @@ mod tests { bump_seed, multicast_ip: Ipv4Addr::UNSPECIFIED, max_bandwidth: 10000, - pub_allowlist: vec![client.get_payer()], - sub_allowlist: vec![client.get_payer()], - publishers: vec![], - subscribers: vec![], status: MulticastGroupStatus::Pending, code: "test".to_string(), tenant_pk: Pubkey::default(), + publisher_count: 0, + subscriber_count: 0, }; let mgroup = multicastgroup.clone(); diff --git a/client/doublezero/src/cli/user.rs b/client/doublezero/src/cli/user.rs index fdd53d2f9..7c23eb76f 100644 --- a/client/doublezero/src/cli/user.rs +++ b/client/doublezero/src/cli/user.rs @@ -1,16 +1,10 @@ use clap::{Args, Subcommand}; -use doublezero_cli::{ - allowlist::user::{ - add::AddUserAllowlistCliCommand, list::ListUserAllowlistCliCommand, - remove::RemoveUserAllowlistCliCommand, - }, - user::{ - create::CreateUserCliCommand, create_subscribe::CreateSubscribeUserCliCommand, - delete::DeleteUserCliCommand, get::GetUserCliCommand, list::ListUserCliCommand, - request_ban::RequestBanUserCliCommand, subscribe::SubscribeUserCliCommand, - update::UpdateUserCliCommand, - }, +use doublezero_cli::user::{ + create::CreateUserCliCommand, create_subscribe::CreateSubscribeUserCliCommand, + delete::DeleteUserCliCommand, get::GetUserCliCommand, list::ListUserCliCommand, + request_ban::RequestBanUserCliCommand, subscribe::SubscribeUserCliCommand, + update::UpdateUserCliCommand, }; #[derive(Args, Debug)] @@ -42,29 +36,7 @@ pub enum UserCommands { /// Delete a user #[command(hide = true)] Delete(DeleteUserCliCommand), - /// Manage user allowlist - #[command()] - Allowlist(UserAllowlistCliCommand), /// Request a ban for a user #[command(hide = true)] RequestBan(RequestBanUserCliCommand), } - -#[derive(Args, Debug)] -pub struct UserAllowlistCliCommand { - #[command(subcommand)] - pub command: UserAllowlistCommands, -} - -#[derive(Debug, Subcommand)] -pub enum UserAllowlistCommands { - /// List user allowlist - #[clap()] - List(ListUserAllowlistCliCommand), - /// Add a user to the allowlist - #[clap()] - Add(AddUserAllowlistCliCommand), - /// Remove a user from the allowlist - #[clap()] - Remove(RemoveUserAllowlistCliCommand), -} diff --git a/client/doublezero/src/command/connect.rs b/client/doublezero/src/command/connect.rs index d5b1925f2..23277538e 100644 --- a/client/doublezero/src/command/connect.rs +++ b/client/doublezero/src/command/connect.rs @@ -96,7 +96,7 @@ impl ProvisioningCliCommand { let (client_ip, client_ip_str) = look_for_ip(&self.client_ip, &spinner).await?; if !check_accesspass(client, client_ip)? { - println!("❌ Unable to find a valid Access Pass for the IP: {client_ip_str}"); + println!("❌ Unable to find a valid Access Pass for the IP: {client_ip_str}",); return Err(eyre::eyre!( "You need to have a valid Access Pass to connect. Please contact support." )); @@ -774,10 +774,6 @@ mod tests { .returning_st(move |_| Ok((Pubkey::new_unique(), global_cfg.clone()))); let payer = fixture.client.get_payer(); - fixture - .client - .expect_list_user_allowlist() - .returning_st(move |_| Ok(vec![payer])); let (accesspass_pk, _) = get_accesspass_pda( &fixture.client.get_program_id(), @@ -794,6 +790,8 @@ mod tests { accesspass_type: AccessPassType::Prepaid, connection_count: 0, status: AccessPassStatus::Requested, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], }; fixture .client @@ -909,10 +907,8 @@ mod tests { max_bandwidth: 10_000_000_000, status: MulticastGroupStatus::Activated, code: code.to_string(), - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![], - subscribers: vec![], + publisher_count: 0, + subscriber_count: 0, }; mcast_groups.insert(pk, group.clone()); (pk, group) diff --git a/client/doublezero/src/main.rs b/client/doublezero/src/main.rs index ac089470e..21526db20 100644 --- a/client/doublezero/src/main.rs +++ b/client/doublezero/src/main.rs @@ -16,7 +16,7 @@ use crate::cli::{ }, link::LinkCommands, location::LocationCommands, - user::{UserAllowlistCommands, UserCommands}, + user::UserCommands, }; use doublezero_cli::{checkversion::check_version, doublezerocommand::CliCommandImpl}; use doublezero_sdk::{DZClient, ProgramVersion}; @@ -185,11 +185,6 @@ async fn main() -> eyre::Result<()> { UserCommands::List(args) => args.execute(&client, &mut handle), UserCommands::Get(args) => args.execute(&client, &mut handle), UserCommands::Delete(args) => args.execute(&client, &mut handle), - UserCommands::Allowlist(command) => match command.command { - UserAllowlistCommands::List(args) => args.execute(&client, &mut handle), - UserAllowlistCommands::Add(args) => args.execute(&client, &mut handle), - UserAllowlistCommands::Remove(args) => args.execute(&client, &mut handle), - }, UserCommands::RequestBan(args) => args.execute(&client, &mut handle), }, Command::Multicast(args) => match args.command { diff --git a/controlplane/controller/internal/controller/server_test.go b/controlplane/controller/internal/controller/server_test.go index 3e84247e8..f01cdfb8a 100644 --- a/controlplane/controller/internal/controller/server_test.go +++ b/controlplane/controller/internal/controller/server_test.go @@ -727,10 +727,6 @@ func TestStateCache(t *testing.T) { { PubKey: [32]uint8{1}, MulticastIp: [4]uint8{239, 0, 0, 1}, - Subscribers: [][32]uint8{ - {1}, - {2}, - }, }, }, Users: []serviceability.User{ @@ -871,10 +867,6 @@ func TestStateCache(t *testing.T) { "4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM": { PubKey: [32]uint8{1}, MulticastIp: [4]uint8{239, 0, 0, 1}, - Subscribers: [][32]uint8{ - {1}, - {2}, - }, }, }, Vpnv4BgpPeers: []BgpPeer{ @@ -1135,10 +1127,6 @@ func TestEndToEnd(t *testing.T) { { PubKey: [32]uint8{1}, MulticastIp: [4]uint8{239, 0, 0, 1}, - Subscribers: [][32]uint8{ - {1}, - {2}, - }, }, }, InterfacesAndPeers: true, @@ -1249,10 +1237,6 @@ func TestEndToEnd(t *testing.T) { { PubKey: [32]uint8{1}, MulticastIp: [4]uint8{239, 0, 0, 1}, - Subscribers: [][32]uint8{ - {1}, - {2}, - }, }, }, InterfacesAndPeers: true, diff --git a/controlplane/doublezero-admin/src/cli/user.rs b/controlplane/doublezero-admin/src/cli/user.rs index fdd53d2f9..7c23eb76f 100644 --- a/controlplane/doublezero-admin/src/cli/user.rs +++ b/controlplane/doublezero-admin/src/cli/user.rs @@ -1,16 +1,10 @@ use clap::{Args, Subcommand}; -use doublezero_cli::{ - allowlist::user::{ - add::AddUserAllowlistCliCommand, list::ListUserAllowlistCliCommand, - remove::RemoveUserAllowlistCliCommand, - }, - user::{ - create::CreateUserCliCommand, create_subscribe::CreateSubscribeUserCliCommand, - delete::DeleteUserCliCommand, get::GetUserCliCommand, list::ListUserCliCommand, - request_ban::RequestBanUserCliCommand, subscribe::SubscribeUserCliCommand, - update::UpdateUserCliCommand, - }, +use doublezero_cli::user::{ + create::CreateUserCliCommand, create_subscribe::CreateSubscribeUserCliCommand, + delete::DeleteUserCliCommand, get::GetUserCliCommand, list::ListUserCliCommand, + request_ban::RequestBanUserCliCommand, subscribe::SubscribeUserCliCommand, + update::UpdateUserCliCommand, }; #[derive(Args, Debug)] @@ -42,29 +36,7 @@ pub enum UserCommands { /// Delete a user #[command(hide = true)] Delete(DeleteUserCliCommand), - /// Manage user allowlist - #[command()] - Allowlist(UserAllowlistCliCommand), /// Request a ban for a user #[command(hide = true)] RequestBan(RequestBanUserCliCommand), } - -#[derive(Args, Debug)] -pub struct UserAllowlistCliCommand { - #[command(subcommand)] - pub command: UserAllowlistCommands, -} - -#[derive(Debug, Subcommand)] -pub enum UserAllowlistCommands { - /// List user allowlist - #[clap()] - List(ListUserAllowlistCliCommand), - /// Add a user to the allowlist - #[clap()] - Add(AddUserAllowlistCliCommand), - /// Remove a user from the allowlist - #[clap()] - Remove(RemoveUserAllowlistCliCommand), -} diff --git a/controlplane/doublezero-admin/src/main.rs b/controlplane/doublezero-admin/src/main.rs index 7981d1379..ad66b74f6 100644 --- a/controlplane/doublezero-admin/src/main.rs +++ b/controlplane/doublezero-admin/src/main.rs @@ -12,7 +12,7 @@ use crate::cli::{ }, link::LinkCommands, location::LocationCommands, - user::{UserAllowlistCommands, UserCommands}, + user::UserCommands, }; use doublezero_cli::{checkversion::check_version, doublezerocommand::CliCommandImpl}; use doublezero_sdk::{DZClient, ProgramVersion}; @@ -155,11 +155,6 @@ async fn main() -> eyre::Result<()> { UserCommands::List(args) => args.execute(&client, &mut handle), UserCommands::Get(args) => args.execute(&client, &mut handle), UserCommands::Delete(args) => args.execute(&client, &mut handle), - UserCommands::Allowlist(command) => match command.command { - UserAllowlistCommands::List(args) => args.execute(&client, &mut handle), - UserAllowlistCommands::Add(args) => args.execute(&client, &mut handle), - UserAllowlistCommands::Remove(args) => args.execute(&client, &mut handle), - }, UserCommands::RequestBan(args) => args.execute(&client, &mut handle), }, Command::Multicast(args) => match args.command { diff --git a/e2e/device_maxusers_rollover_test.go b/e2e/device_maxusers_rollover_test.go index 557a389cc..3b6cb86e7 100644 --- a/e2e/device_maxusers_rollover_test.go +++ b/e2e/device_maxusers_rollover_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" ) - // Test that if the client's nearest device is full, the client will connect to the next nearest device instead func TestE2E_DeviceMaxusersRollover(t *testing.T) { t.Parallel() @@ -110,21 +109,17 @@ func TestE2E_DeviceMaxusersRollover(t *testing.T) { // Apply tc rules to the correct interface for _, command := range [][]string{ {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " root handle 1: prio bands 3"}, - {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " parent 1:1 handle 10: netem delay 0ms"}, - {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " parent 1:2 handle 20: netem delay 10ms"}, - {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " parent 1:3 handle 30: sfq"}, - {"bash", "-c", "tc filter add dev " + cyoaInterface + " protocol ip parent 1:0 prio 1 u32 match ip dst " + device1.CYOANetworkIP + "/32 flowid 1:1"}, - {"bash", "-c", "tc filter add dev " + cyoaInterface + " protocol ip parent 1:0 prio 2 u32 match ip dst " + device2.CYOANetworkIP + "/32 flowid 1:2"}, - {"bash", "-c", "tc filter add dev " + cyoaInterface + " protocol ip parent 1:0 prio 3 u32 match ip dst 0.0.0.0/0 flowid 1:3"}, + {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " parent 1:1 handle 10: netem delay 0ms"}, + {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " parent 1:2 handle 20: netem delay 10ms"}, + {"bash", "-c", "tc qdisc add dev " + cyoaInterface + " parent 1:3 handle 30: sfq"}, + {"bash", "-c", "tc filter add dev " + cyoaInterface + " protocol ip parent 1:0 prio 1 u32 match ip dst " + device1.CYOANetworkIP + "/32 flowid 1:1"}, + {"bash", "-c", "tc filter add dev " + cyoaInterface + " protocol ip parent 1:0 prio 2 u32 match ip dst " + device2.CYOANetworkIP + "/32 flowid 1:2"}, + {"bash", "-c", "tc filter add dev " + cyoaInterface + " protocol ip parent 1:0 prio 3 u32 match ip dst 0.0.0.0/0 flowid 1:3"}, } { _, err = client.Exec(t.Context(), command) require.NoError(t, err) } - // Add clients to user allowlist so they can open user connections. - log.Info("==> Adding clients to user allowlist") - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client.Pubkey}) - require.NoError(t, err) log.Info("--> Client added to user allowlist") // Set access pass for the client. diff --git a/e2e/device_stress_test.go b/e2e/device_stress_test.go index 4ca9404fc..eda69d189 100644 --- a/e2e/device_stress_test.go +++ b/e2e/device_stress_test.go @@ -132,14 +132,6 @@ func TestE2E_DeviceStress(t *testing.T) { log.Debug(fmt.Sprintf("--> Created client %d", i+1), "pubkey", client.Pubkey, "cyoaIP", client.CYOANetworkIP) - // Add to allowlist - log.Info(fmt.Sprintf("Adding client %d to allowlist", i+1)) - _, err = dn.Manager.Exec(t.Context(), []string{ - "doublezero", "user", "allowlist", "add", - "--pubkey", client.Pubkey, - }) - require.NoError(t, err) - // Set access pass cmd := fmt.Sprintf("doublezero access-pass set --accesspass-type prepaid --client-ip %s --user-payer %s --last-access-epoch 99999", client.CYOANetworkIP, client.Pubkey) diff --git a/e2e/internal/rpc/agent.go b/e2e/internal/rpc/agent.go index bcdc5555f..83d351b3a 100644 --- a/e2e/internal/rpc/agent.go +++ b/e2e/internal/rpc/agent.go @@ -1,6 +1,7 @@ package rpc import ( + "bufio" "context" "encoding/json" "fmt" @@ -378,8 +379,14 @@ func (q *QAAgent) MulticastAllowListAdd(ctx context.Context, req *pb.MulticastAl if req.GetMode() == pb.MulticastAllowListAddRequest_SUBSCRIBER { mode = "subscriber" } - q.log.Info("Received MulticastAllowListAdd request", "pubkey", req.GetPubkey(), "code", req.GetCode(), "mode", mode) - cmd := exec.Command("doublezero", "multicast", "group", "allowlist", mode, "add", "--pubkey", req.GetPubkey(), "--code", req.GetCode()) + + ipStr, err := getPublicIPv4() + if err != nil { + return nil, fmt.Errorf("failed to get public IPv4 address: %w", err) + } + + q.log.Info("Received MulticastAllowListAdd request", "pubkey", req.GetPubkey(), "client-ip", ipStr, "code", req.GetCode(), "mode", mode) + cmd := exec.Command("doublezero", "multicast", "group", "allowlist", mode, "add", "--pubkey", req.GetPubkey(), "--client-ip", ipStr, "--code", req.GetCode()) result, err := runCmd(cmd) if err != nil { q.log.Error("Failed to add multicast allowlist entry", "error", err, "output", result.Output) @@ -472,6 +479,58 @@ func (q *QAAgent) MulticastReport(ctx context.Context, req *pb.MulticastReportRe return &pb.MulticastReportResult{Reports: reports}, nil } +func getPublicIPv4() (string, error) { + // Resolver IPv4 de ifconfig.me:80 + addrs, err := net.LookupIP("ifconfig.me") + if err != nil { + return "", fmt.Errorf("error resolviendo host: %w", err) + } + + var ipv4 net.IP + for _, ip := range addrs { + if ip.To4() != nil { + ipv4 = ip + break + } + } + if ipv4 == nil { + return "", fmt.Errorf("no se encontró IPv4 para ifconfig.me") + } + + // Conectar al host + addr := net.JoinHostPort(ipv4.String(), "80") + conn, err := net.DialTimeout("tcp", addr, 5*time.Second) + if err != nil { + return "", fmt.Errorf("error conectando: %w", err) + } + defer conn.Close() + + // Enviar request HTTP + req := "GET /ip HTTP/1.1\r\nHost: ifconfig.me\r\nConnection: close\r\n\r\n" + if _, err := conn.Write([]byte(req)); err != nil { + return "", fmt.Errorf("error enviando request: %w", err) + } + + // Leer respuesta + reader := bufio.NewReader(conn) + var response strings.Builder + for { + line, err := reader.ReadString('\n') + response.WriteString(line) + if err != nil { + break + } + } + + // Extraer cuerpo + parts := strings.SplitN(response.String(), "\r\n\r\n", 2) + if len(parts) < 2 { + return "", fmt.Errorf("no se pudo parsear respuesta") + } + ip := strings.TrimSpace(parts[1]) + return ip, nil +} + // runCmd executes a command and returns the result in a structured format. func runCmd(cmd *exec.Cmd) (*pb.Result, error) { output, err := cmd.CombinedOutput() diff --git a/e2e/main_test.go b/e2e/main_test.go index 49ab507f4..aec2b8ab9 100644 --- a/e2e/main_test.go +++ b/e2e/main_test.go @@ -239,10 +239,6 @@ func (dn *TestDevnet) Start(t *testing.T) (*devnet.Device, *devnet.Client) { }) require.NoError(t, err) - // Add client to the user allowlist. - _, err = dn.Manager.Exec(ctx, []string{"bash", "-c", "doublezero user allowlist add --pubkey " + client.Pubkey}) - require.NoError(t, err) - // Set access pass for the client. _, err = dn.Manager.Exec(ctx, []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client.CYOANetworkIP + " --user-payer " + client.Pubkey}) require.NoError(t, err) @@ -306,12 +302,12 @@ func (dn *TestDevnet) CreateMulticastGroupOnchain(t *testing.T, client *devnet.C doublezero multicast group list echo "==> Add me to multicast group allowlist" - doublezero multicast group allowlist publisher add --code ` + multicastGroupCode + ` --pubkey me - doublezero multicast group allowlist subscriber add --code ` + multicastGroupCode + ` --pubkey me + doublezero multicast group allowlist publisher add --code ` + multicastGroupCode + ` --user-payer me --client-ip ` + client.CYOANetworkIP + ` + doublezero multicast group allowlist subscriber add --code ` + multicastGroupCode + ` --user-payer me --client-ip ` + client.CYOANetworkIP + ` echo "==> Add client pubkey to multicast group allowlist" - doublezero multicast group allowlist publisher add --code ` + multicastGroupCode + ` --pubkey ` + client.Pubkey + ` - doublezero multicast group allowlist subscriber add --code ` + multicastGroupCode + ` --pubkey ` + client.Pubkey + ` + doublezero multicast group allowlist publisher add --code ` + multicastGroupCode + ` --user-payer ` + client.Pubkey + ` --client-ip ` + client.CYOANetworkIP + ` + doublezero multicast group allowlist subscriber add --code ` + multicastGroupCode + ` --user-payer ` + client.Pubkey + ` --client-ip ` + client.CYOANetworkIP + ` `}) require.NoError(t, err) } diff --git a/e2e/multi_client_test.go b/e2e/multi_client_test.go index f9d82838f..7bcfffd47 100644 --- a/e2e/multi_client_test.go +++ b/e2e/multi_client_test.go @@ -95,20 +95,14 @@ func TestE2E_MultiClient(t *testing.T) { require.NoError(t, err) log.Info("--> Finished waiting for client latency results") - // Add clients to user allowlist so they can open user connections. - log.Info("==> Adding clients to user allowlist") - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client1.Pubkey}) - require.NoError(t, err) - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client2.Pubkey}) - require.NoError(t, err) - log.Info("--> Clients added to user allowlist") - + log.Info("==> Add clients to user Access Pass") // Set access pass for the client. _, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client1.CYOANetworkIP + " --user-payer " + client1.Pubkey}) require.NoError(t, err) // Set access pass for the client. _, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client2.CYOANetworkIP + " --user-payer " + client2.Pubkey}) require.NoError(t, err) + log.Info("--> Clients added to user Access Pass") // Run IBRL workflow test. if !t.Run("ibrl", func(t *testing.T) { diff --git a/e2e/no_ifaces_peers_test.go b/e2e/no_ifaces_peers_test.go index ad6e495ba..072c10c49 100644 --- a/e2e/no_ifaces_peers_test.go +++ b/e2e/no_ifaces_peers_test.go @@ -97,20 +97,15 @@ func TestE2E_Controller_NoIfacesAndPeers(t *testing.T) { require.NoError(t, err) log.Info("--> Finished waiting for client latency results") - // Add clients to user allowlist so they can open user connections. - log.Info("==> Adding clients to user allowlist") - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client1.Pubkey}) - require.NoError(t, err) - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client2.Pubkey}) - require.NoError(t, err) - log.Info("--> Clients added to user allowlist") - + // Add clients to user Access Pass so they can open user connections. + log.Info("==> Adding clients to Access Pass") // Set access pass for the client. _, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client1.CYOANetworkIP + " --user-payer " + client1.Pubkey}) require.NoError(t, err) // Set access pass for the client. _, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client2.CYOANetworkIP + " --user-payer " + client2.Pubkey}) require.NoError(t, err) + log.Info("--> Finished adding clients to Access Pass") // Run IBRL with allocated IP workflow test. if !t.Run("ibrl_with_allocated_ip", func(t *testing.T) { diff --git a/e2e/proto/qa/agent.proto b/e2e/proto/qa/agent.proto index ebb2a278a..aaaa98350 100644 --- a/e2e/proto/qa/agent.proto +++ b/e2e/proto/qa/agent.proto @@ -64,6 +64,7 @@ message MulticastAllowListAddRequest { MulticastMode mode = 1; string code = 2; string pubkey = 3; + string client_ip = 4; } message Result { diff --git a/e2e/proto/qa/gen/pb-go/agent.pb.go b/e2e/proto/qa/gen/pb-go/agent.pb.go index f69649890..5613d82a1 100644 --- a/e2e/proto/qa/gen/pb-go/agent.pb.go +++ b/e2e/proto/qa/gen/pb-go/agent.pb.go @@ -7,11 +7,12 @@ package qa import ( + reflect "reflect" + sync "sync" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" ) const ( diff --git a/e2e/qa_test.go b/e2e/qa_test.go index 60e9d2718..9089ca7c4 100644 --- a/e2e/qa_test.go +++ b/e2e/qa_test.go @@ -254,10 +254,15 @@ func TestConnectivityMulticast(t *testing.T) { client, err := getQAClient(publisher) require.NoError(t, err, "Failed to create QA client") + ips, err := net.LookupIP(publisher) + require.NoError(t, err, "Failed to lookup IP for publisher") + ownerIP := ips[0].String() + req := &pb.MulticastAllowListAddRequest{ - Mode: pb.MulticastAllowListAddRequest_PUBLISHER, - Code: code, - Pubkey: ownerPubKey, + Mode: pb.MulticastAllowListAddRequest_PUBLISHER, + Code: code, + Pubkey: ownerPubKey, + ClientIp: ownerIP, } result, err := client.MulticastAllowListAdd(ctx, req) require.NoError(t, err, "MulticastAllowListAdd failed") @@ -267,9 +272,10 @@ func TestConnectivityMulticast(t *testing.T) { t.Logf("Multicast group %s added to allow list for publisher %s", code, ownerPubKey) req = &pb.MulticastAllowListAddRequest{ - Mode: pb.MulticastAllowListAddRequest_SUBSCRIBER, - Code: code, - Pubkey: ownerPubKey, + Mode: pb.MulticastAllowListAddRequest_SUBSCRIBER, + Code: code, + Pubkey: ownerPubKey, + ClientIp: ownerIP, } result, err = client.MulticastAllowListAdd(ctx, req) require.NoError(t, err, "MulticastAllowListAdd failed") diff --git a/e2e/user_ban_test.go b/e2e/user_ban_test.go index a6e3dace5..f17a8b79f 100644 --- a/e2e/user_ban_test.go +++ b/e2e/user_ban_test.go @@ -99,20 +99,15 @@ func TestE2E_UserBan(t *testing.T) { require.NoError(t, err) log.Info("--> Finished waiting for client latency results") - // Add clients to user allowlist so they can open user connections. - log.Info("==> Adding clients to user allowlist") - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client1.Pubkey}) - require.NoError(t, err) - _, err = dn.Manager.Exec(t.Context(), []string{"doublezero", "user", "allowlist", "add", "--pubkey", client2.Pubkey}) - require.NoError(t, err) - log.Info("--> Clients added to user allowlist") - + // Add clients to user Access Pass so they can open user connections. + log.Info("==> Adding clients to Access Pass") // Set access pass for the client. _, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client1.CYOANetworkIP + " --user-payer " + client1.Pubkey}) require.NoError(t, err) // Set access pass for the client. _, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero access-pass set --accesspass-type prepaid --client-ip " + client2.CYOANetworkIP + " --user-payer " + client2.Pubkey}) require.NoError(t, err) + log.Info("--> Clients added to user Access Pass") // Run IBRL workflow test. if !t.Run("user-ban-ibrl", func(t *testing.T) { diff --git a/smartcontract/cli/src/accesspass/list.rs b/smartcontract/cli/src/accesspass/list.rs index 23452a4ec..90ffb4ea5 100644 --- a/smartcontract/cli/src/accesspass/list.rs +++ b/smartcontract/cli/src/accesspass/list.rs @@ -1,7 +1,9 @@ use crate::doublezerocommand::CliCommand; use clap::Args; use doublezero_program_common::serializer; -use doublezero_sdk::commands::accesspass::list::ListAccessPassCommand; +use doublezero_sdk::commands::{ + accesspass::list::ListAccessPassCommand, multicastgroup::list::ListMulticastGroupCommand, +}; use doublezero_serviceability::state::accesspass::{AccessPassStatus, AccessPassType}; use serde::Serialize; use solana_sdk::pubkey::Pubkey; @@ -36,6 +38,7 @@ pub struct AccessPassDisplay { pub ip: Ipv4Addr, #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] pub user_payer: Pubkey, + pub multicast: String, pub last_access_epoch: String, pub remaining_epoch: String, pub connections: u16, @@ -48,6 +51,8 @@ impl ListAccessPassCliCommand { pub fn execute(self, client: &C, out: &mut W) -> eyre::Result<()> { let epoch = client.get_epoch()?; + let mgroups = client.list_multicastgroup(ListMulticastGroupCommand {})?; + let binding = client.list_accesspass(ListAccessPassCommand)?; let mut access_passes = binding.iter().collect::>(); @@ -79,6 +84,25 @@ impl ListAccessPassCliCommand { accesspass_type: access_pass.accesspass_type.to_string(), ip: access_pass.client_ip, user_payer: access_pass.user_payer, + multicast: { + let mut list = vec![]; + for mg_pub in &access_pass.mgroup_pub_allowlist { + if let Some(mg) = mgroups.get(mg_pub) { + list.push(format!("P:{}", mg.code)); + } else { + list.push(format!("P:{mg_pub}")); + } + } + for mg_sub in &access_pass.mgroup_sub_allowlist { + if let Some(mg) = mgroups.get(mg_sub) { + list.push(format!("S:{}", mg.code)); + } else { + list.push(format!("S:{mg_sub}")); + } + } + list.join(", ") + }, + last_access_epoch: if access_pass.last_access_epoch == u64::MAX { "MAX".to_string() } else { @@ -130,6 +154,21 @@ mod tests { fn test_cli_accesspass_list() { let mut client = create_test_client(); + let mgroup_pubkey = Pubkey::from_str_const("1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM"); + let mgroup = doublezero_sdk::MulticastGroup { + account_type: AccountType::MulticastGroup, + index: 1, + bump_seed: 1, + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), + tenant_pk: Pubkey::new_unique(), + multicast_ip: [239, 0, 0, 1].into(), + max_bandwidth: 1000000000, + status: doublezero_sdk::MulticastGroupStatus::Activated, + code: "test".to_string(), + publisher_count: 5, + subscriber_count: 10, + }; + let access1_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"); let access1 = AccessPass { account_type: AccountType::AccessPass, @@ -141,6 +180,8 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![mgroup_pubkey], + mgroup_sub_allowlist: vec![], }; let access2_pubkey = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"); @@ -156,9 +197,16 @@ mod tests { owner: Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB"), connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![mgroup_pubkey], }; client.expect_get_epoch().returning(move || Ok(123)); + client.expect_list_multicastgroup().returning(move |_| { + let mut mgroups = HashMap::new(); + mgroups.insert(mgroup_pubkey, mgroup.clone()); + Ok(mgroups) + }); client.expect_list_accesspass().returning(move |_| { let mut access_passes = HashMap::new(); access_passes.insert(access1_pubkey, access1.clone()); @@ -177,7 +225,7 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, " account | accesspass_type | ip | user_payer | last_access_epoch | remaining_epoch | connections | status | owner \n 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB | solana_validator: 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB | 1.2.3.4 | 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB | 123 | 113 | 0 | connected | 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB \n"); + assert_eq!(output_str, " account | accesspass_type | ip | user_payer | multicast | last_access_epoch | remaining_epoch | connections | status | owner \n 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB | solana_validator: 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB | 1.2.3.4 | 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB | S:test | 123 | 113 | 0 | connected | 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB \n"); let mut output = Vec::new(); let res = ListAccessPassCliCommand { @@ -190,6 +238,6 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "[{\"account\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\",\"accesspass_type\":\"solana_validator: 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\",\"ip\":\"1.2.3.4\",\"user_payer\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\",\"last_access_epoch\":\"123\",\"remaining_epoch\":\"113\",\"connections\":0,\"status\":\"Connected\",\"owner\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\"}]\n"); + assert_eq!(output_str, "[{\"account\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\",\"accesspass_type\":\"solana_validator: 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\",\"ip\":\"1.2.3.4\",\"user_payer\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\",\"multicast\":\"S:test\",\"last_access_epoch\":\"123\",\"remaining_epoch\":\"113\",\"connections\":0,\"status\":\"Connected\",\"owner\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB\"}]\n"); } } diff --git a/smartcontract/cli/src/allowlist/mod.rs b/smartcontract/cli/src/allowlist/mod.rs index 6ac2c0cd1..68a8688a2 100644 --- a/smartcontract/cli/src/allowlist/mod.rs +++ b/smartcontract/cli/src/allowlist/mod.rs @@ -1,3 +1,2 @@ pub mod device; pub mod foundation; -pub mod user; diff --git a/smartcontract/cli/src/doublezerocommand.rs b/smartcontract/cli/src/doublezerocommand.rs index 07355232e..0eeba5ddf 100644 --- a/smartcontract/cli/src/doublezerocommand.rs +++ b/smartcontract/cli/src/doublezerocommand.rs @@ -14,10 +14,6 @@ use doublezero_sdk::{ add::AddFoundationAllowlistCommand, list::ListFoundationAllowlistCommand, remove::RemoveFoundationAllowlistCommand, }, - user::{ - add::AddUserAllowlistCommand, list::ListUserAllowlistCommand, - remove::RemoveUserAllowlistCommand, - }, }, contributor::{ create::CreateContributorCommand, delete::DeleteContributorCommand, @@ -67,12 +63,10 @@ use doublezero_sdk::{ allowlist::{ publisher::{ add::AddMulticastGroupPubAllowlistCommand, - list::ListMulticastGroupPubAllowlistCommand, remove::RemoveMulticastGroupPubAllowlistCommand, }, subscriber::{ add::AddMulticastGroupSubAllowlistCommand, - list::ListMulticastGroupSubAllowlistCommand, remove::RemoveMulticastGroupSubAllowlistCommand, }, }, @@ -207,7 +201,6 @@ pub trait CliCommand { cmd: ListFoundationAllowlistCommand, ) -> eyre::Result>; fn list_device_allowlist(&self, cmd: ListDeviceAllowlistCommand) -> eyre::Result>; - fn list_user_allowlist(&self, cmd: ListUserAllowlistCommand) -> eyre::Result>; fn add_foundation_allowlist( &self, cmd: AddFoundationAllowlistCommand, @@ -219,9 +212,6 @@ pub trait CliCommand { fn add_device_allowlist(&self, cmd: AddDeviceAllowlistCommand) -> eyre::Result; fn remove_device_allowlist(&self, cmd: RemoveDeviceAllowlistCommand) -> eyre::Result; - fn add_user_allowlist(&self, cmd: AddUserAllowlistCommand) -> eyre::Result; - fn remove_user_allowlist(&self, cmd: RemoveUserAllowlistCommand) -> eyre::Result; - fn create_multicastgroup( &self, cmd: CreateMulticastGroupCommand, @@ -265,15 +255,6 @@ pub trait CliCommand { &self, cmd: RemoveMulticastGroupSubAllowlistCommand, ) -> eyre::Result; - fn list_multicastgroup_pub_allowlist( - &self, - cmd: ListMulticastGroupPubAllowlistCommand, - ) -> eyre::Result>; - fn list_multicastgroup_sub_allowlist( - &self, - cmd: ListMulticastGroupSubAllowlistCommand, - ) -> eyre::Result>; - fn set_accesspass(&self, cmd: SetAccessPassCommand) -> eyre::Result; fn get_accesspass(&self, cmd: GetAccessPassCommand) -> eyre::Result<(Pubkey, AccessPass)>; fn list_accesspass( @@ -530,9 +511,6 @@ impl CliCommand for CliCommandImpl<'_> { fn list_device_allowlist(&self, cmd: ListDeviceAllowlistCommand) -> eyre::Result> { cmd.execute(self.client) } - fn list_user_allowlist(&self, cmd: ListUserAllowlistCommand) -> eyre::Result> { - cmd.execute(self.client) - } fn add_foundation_allowlist( &self, cmd: AddFoundationAllowlistCommand, @@ -554,12 +532,6 @@ impl CliCommand for CliCommandImpl<'_> { ) -> eyre::Result { cmd.execute(self.client) } - fn add_user_allowlist(&self, cmd: AddUserAllowlistCommand) -> eyre::Result { - cmd.execute(self.client) - } - fn remove_user_allowlist(&self, cmd: RemoveUserAllowlistCommand) -> eyre::Result { - cmd.execute(self.client) - } fn create_multicastgroup( &self, cmd: CreateMulticastGroupCommand, @@ -629,18 +601,6 @@ impl CliCommand for CliCommandImpl<'_> { ) -> eyre::Result { cmd.execute(self.client) } - fn list_multicastgroup_pub_allowlist( - &self, - cmd: ListMulticastGroupPubAllowlistCommand, - ) -> eyre::Result> { - cmd.execute(self.client) - } - fn list_multicastgroup_sub_allowlist( - &self, - cmd: ListMulticastGroupSubAllowlistCommand, - ) -> eyre::Result> { - cmd.execute(self.client) - } fn set_accesspass(&self, cmd: SetAccessPassCommand) -> eyre::Result { cmd.execute(self.client) } diff --git a/smartcontract/cli/src/multicastgroup/allowlist/publisher/add.rs b/smartcontract/cli/src/multicastgroup/allowlist/publisher/add.rs index 30274234e..979c89ef5 100644 --- a/smartcontract/cli/src/multicastgroup/allowlist/publisher/add.rs +++ b/smartcontract/cli/src/multicastgroup/allowlist/publisher/add.rs @@ -6,16 +6,19 @@ use crate::{ use clap::Args; use doublezero_sdk::commands::multicastgroup::allowlist::publisher::add::AddMulticastGroupPubAllowlistCommand; use solana_sdk::pubkey::Pubkey; -use std::{io::Write, str::FromStr}; +use std::{io::Write, net::Ipv4Addr, str::FromStr}; #[derive(Args, Debug)] pub struct AddMulticastGroupPubAllowlistCliCommand { /// Multicast group code to add the publisher to #[arg(long, value_parser = validate_code)] pub code: String, + /// Client IP address in IPv4 format + #[arg(long)] + pub client_ip: Ipv4Addr, /// Publisher Pubkey or 'me' for current payer #[arg(long, value_parser = validate_pubkey)] - pub pubkey: String, + pub user_payer: String, } impl AddMulticastGroupPubAllowlistCliCommand { @@ -24,17 +27,18 @@ impl AddMulticastGroupPubAllowlistCliCommand { client.check_requirements(CHECK_ID_JSON | CHECK_BALANCE)?; let pubkey = { - if self.pubkey.eq_ignore_ascii_case("me") { + if self.user_payer.eq_ignore_ascii_case("me") { client.get_payer() } else { - Pubkey::from_str(&self.pubkey)? + Pubkey::from_str(&self.user_payer)? } }; let signature = client.add_multicastgroup_pub_allowlist(AddMulticastGroupPubAllowlistCommand { pubkey_or_code: self.code, - pubkey, + client_ip: self.client_ip, + user_payer: pubkey, })?; writeln!(out, "Signature: {signature}")?; @@ -65,6 +69,8 @@ mod tests { 100, 221, 20, 137, 4, 5, ]); + let client_ip = [100, 0, 0, 1].into(); + client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) @@ -73,7 +79,8 @@ mod tests { .expect_add_multicastgroup_pub_allowlist() .with(predicate::eq(AddMulticastGroupPubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + user_payer: pubkey, + client_ip, })) .returning(move |_| Ok(signature)); @@ -81,7 +88,8 @@ mod tests { let mut output = Vec::new(); let res = AddMulticastGroupPubAllowlistCliCommand { code: "test_code".to_string(), - pubkey: pubkey.to_string(), + client_ip, + user_payer: pubkey.to_string(), } .execute(&client, &mut output); assert!(res.is_ok()); diff --git a/smartcontract/cli/src/multicastgroup/allowlist/publisher/list.rs b/smartcontract/cli/src/multicastgroup/allowlist/publisher/list.rs index 13741701e..ce9748e7f 100644 --- a/smartcontract/cli/src/multicastgroup/allowlist/publisher/list.rs +++ b/smartcontract/cli/src/multicastgroup/allowlist/publisher/list.rs @@ -1,7 +1,13 @@ use crate::doublezerocommand::CliCommand; +use ::serde::Serialize; use clap::Args; -use doublezero_sdk::commands::multicastgroup::allowlist::publisher::list::ListMulticastGroupPubAllowlistCommand; -use std::io::Write; +use doublezero_program_common::serializer; +use doublezero_sdk::commands::{ + accesspass::list::ListAccessPassCommand, multicastgroup::get::GetMulticastGroupCommand, +}; +use solana_sdk::pubkey::Pubkey; +use std::{io::Write, net::Ipv4Addr}; +use tabled::{settings::Style, Table, Tabled}; #[derive(Args, Debug)] pub struct ListMulticastGroupPubAllowlistCliCommand { @@ -16,33 +22,46 @@ pub struct ListMulticastGroupPubAllowlistCliCommand { pub json_compact: bool, } +#[derive(Tabled, Serialize)] +pub struct MulticastAllowlistDisplay { + #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] + pub account: Pubkey, + pub multicast_group: String, + pub client_ip: Ipv4Addr, + #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] + pub user_payer: Pubkey, +} + impl ListMulticastGroupPubAllowlistCliCommand { pub fn execute(self, client: &C, out: &mut W) -> eyre::Result<()> { - let list = - client.list_multicastgroup_pub_allowlist(ListMulticastGroupPubAllowlistCommand { - pubkey_or_code: self.code.clone(), - })?; - - if self.json || self.json_compact { - let list = list - .into_iter() - .map(|pubkey| pubkey.to_string()) - .collect::>(); - - let json = { - if self.json_compact { - serde_json::to_string(&list)? - } else { - serde_json::to_string_pretty(&list)? - } - }; - writeln!(out, "{json}")?; + let (mgroup_pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + pubkey_or_code: self.code.clone(), + })?; + + let list_accesspass = client.list_accesspass(ListAccessPassCommand {})?; + + let mga_displays = list_accesspass + .into_iter() + .filter(|(_, accesspass)| accesspass.mgroup_pub_allowlist.contains(&mgroup_pubkey)) + .map(|(_, accesspass)| MulticastAllowlistDisplay { + account: mgroup_pubkey, + multicast_group: mgroup.code.clone(), + client_ip: accesspass.client_ip, + user_payer: accesspass.user_payer, + }) + .collect::>(); + + let res = if self.json { + serde_json::to_string_pretty(&mga_displays)? + } else if self.json_compact { + serde_json::to_string(&mga_displays)? } else { - writeln!(out, "Pubkeys:")?; - for user in list { - writeln!(out, "\t{user}")?; - } - } + Table::new(mga_displays) + .with(Style::psql().remove_horizontals()) + .to_string() + }; + + writeln!(out, "{res}")?; Ok(()) } @@ -50,33 +69,95 @@ impl ListMulticastGroupPubAllowlistCliCommand { #[cfg(test)] mod tests { + use std::collections::HashMap; + use crate::{ multicastgroup::allowlist::publisher::list::ListMulticastGroupPubAllowlistCliCommand, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, tests::utils::create_test_client, }; - use doublezero_sdk::commands::multicastgroup::allowlist::publisher::list::ListMulticastGroupPubAllowlistCommand; + use doublezero_sdk::{ + commands::{ + accesspass::list::ListAccessPassCommand, multicastgroup::get::GetMulticastGroupCommand, + }, + AccountType, MulticastGroup, + }; + use doublezero_serviceability::state::accesspass::{ + AccessPass, AccessPassStatus, AccessPassType, + }; use mockall::predicate; use solana_sdk::pubkey::Pubkey; #[test] - fn test_cli_user_allowlist_list() { + fn test_cli_multicast_publisher_allowlist_list() { let mut client = create_test_client(); - let pubkey1 = Pubkey::from_str_const("1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM"); - let pubkey2 = Pubkey::from_str_const("1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"); - let pubkey3 = Pubkey::from_str_const("11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3"); + let user_payer = Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo4"); + + let mgroup_pubkey = Pubkey::from_str_const("1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM"); + let mgroup = MulticastGroup { + account_type: AccountType::MulticastGroup, + index: 1, + bump_seed: 1, + code: "test".to_string(), + multicast_ip: [239, 0, 0, 1].into(), + max_bandwidth: 1000000000, + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), + tenant_pk: Pubkey::default(), + status: doublezero_sdk::MulticastGroupStatus::Activated, + publisher_count: 5, + subscriber_count: 10, + }; + + let accesspass1_pk = Pubkey::from_str_const("1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"); + let accesspass1 = AccessPass { + account_type: AccountType::AccessPass, + bump_seed: 1, + client_ip: [100, 0, 0, 1].into(), + accesspass_type: AccessPassType::Prepaid, + last_access_epoch: 1234, + user_payer, + mgroup_pub_allowlist: vec![mgroup_pubkey], + mgroup_sub_allowlist: vec![], + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo7"), + connection_count: 0, + status: AccessPassStatus::Requested, + }; + + let accesspass2_pk = Pubkey::from_str_const("11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3"); + let accesspass2 = AccessPass { + account_type: AccountType::AccessPass, + bump_seed: 1, + client_ip: [100, 0, 0, 1].into(), + accesspass_type: AccessPassType::Prepaid, + last_access_epoch: 1234, + user_payer, + mgroup_pub_allowlist: vec![mgroup_pubkey], + mgroup_sub_allowlist: vec![], + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo5"), + connection_count: 0, + status: AccessPassStatus::Requested, + }; client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) .returning(|_| Ok(())); client - .expect_list_multicastgroup_pub_allowlist() - .with(predicate::eq(ListMulticastGroupPubAllowlistCommand { + .expect_get_multicastgroup() + .with(predicate::eq(GetMulticastGroupCommand { pubkey_or_code: "test".to_string(), })) - .returning(move |_| Ok(vec![pubkey1, pubkey2, pubkey3])); + .returning(move |_| Ok((mgroup_pubkey, mgroup.clone()))); + client + .expect_list_accesspass() + .with(predicate::eq(ListAccessPassCommand {})) + .returning(move |_| { + let mut list: HashMap = HashMap::new(); + list.insert(accesspass1_pk, accesspass1.clone()); + list.insert(accesspass2_pk, accesspass2.clone()); + Ok(list) + }); /*****************************************************************************************************/ let mut output = Vec::new(); @@ -89,7 +170,7 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); assert_eq!( - output_str,"Pubkeys:\n\t1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\n\t1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh\n\t11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3\n" + output_str," account | multicast_group | client_ip | user_payer \n 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM | test | 100.0.0.1 | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo4 \n 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM | test | 100.0.0.1 | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo4 \n" ); let mut output = Vec::new(); @@ -102,7 +183,7 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); assert_eq!( - output_str,"[\"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\",\"1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh\",\"11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3\"]\n" + output_str,"[{\"account\":\"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\",\"multicast_group\":\"test\",\"client_ip\":\"100.0.0.1\",\"user_payer\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo4\"},{\"account\":\"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\",\"multicast_group\":\"test\",\"client_ip\":\"100.0.0.1\",\"user_payer\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo4\"}]\n" ); } } diff --git a/smartcontract/cli/src/multicastgroup/allowlist/publisher/remove.rs b/smartcontract/cli/src/multicastgroup/allowlist/publisher/remove.rs index 50ad3c8e0..6ff1b970f 100644 --- a/smartcontract/cli/src/multicastgroup/allowlist/publisher/remove.rs +++ b/smartcontract/cli/src/multicastgroup/allowlist/publisher/remove.rs @@ -5,16 +5,19 @@ use crate::{ use clap::Args; use doublezero_sdk::commands::multicastgroup::allowlist::publisher::remove::RemoveMulticastGroupPubAllowlistCommand; use solana_sdk::pubkey::Pubkey; -use std::{io::Write, str::FromStr}; +use std::{io::Write, net::Ipv4Addr, str::FromStr}; #[derive(Args, Debug)] pub struct RemoveMulticastGroupPubAllowlistCliCommand { /// Multicast group code or pubkey to remove publisher allowlist for #[arg(long)] pub code: String, + /// Client IP address in IPv4 format + #[arg(long)] + pub client_ip: Ipv4Addr, /// Publisher Pubkey or 'me' for current payer #[arg(long)] - pub pubkey: String, + pub user_payer: String, } impl RemoveMulticastGroupPubAllowlistCliCommand { @@ -22,18 +25,19 @@ impl RemoveMulticastGroupPubAllowlistCliCommand { // Check requirements client.check_requirements(CHECK_ID_JSON | CHECK_BALANCE)?; - let pubkey = { - if self.pubkey.eq_ignore_ascii_case("me") { + let user_payer = { + if self.user_payer.eq_ignore_ascii_case("me") { client.get_payer() } else { - Pubkey::from_str(&self.pubkey)? + Pubkey::from_str(&self.user_payer)? } }; let signature = client.remove_multicastgroup_pub_allowlist( RemoveMulticastGroupPubAllowlistCommand { pubkey_or_code: self.code, - pubkey, + client_ip: self.client_ip, + user_payer, }, )?; writeln!(out, "Signature: {signature}")?; @@ -65,6 +69,8 @@ mod tests { 100, 221, 20, 137, 4, 5, ]); + let client_ip = [100, 0, 0, 1].into(); + client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) @@ -73,7 +79,8 @@ mod tests { .expect_remove_multicastgroup_pub_allowlist() .with(predicate::eq(RemoveMulticastGroupPubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip, + user_payer: pubkey, })) .returning(move |_| Ok(signature)); @@ -81,7 +88,8 @@ mod tests { let mut output = Vec::new(); let res = RemoveMulticastGroupPubAllowlistCliCommand { code: "test_code".to_string(), - pubkey: pubkey.to_string(), + client_ip, + user_payer: pubkey.to_string(), } .execute(&client, &mut output); assert!(res.is_ok()); diff --git a/smartcontract/cli/src/multicastgroup/allowlist/subscriber/add.rs b/smartcontract/cli/src/multicastgroup/allowlist/subscriber/add.rs index e6261c624..299ce8bc7 100644 --- a/smartcontract/cli/src/multicastgroup/allowlist/subscriber/add.rs +++ b/smartcontract/cli/src/multicastgroup/allowlist/subscriber/add.rs @@ -6,16 +6,19 @@ use crate::{ use clap::Args; use doublezero_sdk::commands::multicastgroup::allowlist::subscriber::add::AddMulticastGroupSubAllowlistCommand; use solana_sdk::pubkey::Pubkey; -use std::{io::Write, str::FromStr}; +use std::{io::Write, net::Ipv4Addr, str::FromStr}; #[derive(Args, Debug)] pub struct AddMulticastGroupSubAllowlistCliCommand { /// Multicast group code or pubkey to add subscriber allowlist for #[arg(long, value_parser = validate_code)] pub code: String, + /// Client IP address in IPv4 format + #[arg(long)] + pub client_ip: Ipv4Addr, /// Subscriber Pubkey or 'me' for current payer #[arg(long, value_parser = validate_pubkey)] - pub pubkey: String, + pub user_payer: String, } impl AddMulticastGroupSubAllowlistCliCommand { @@ -23,18 +26,19 @@ impl AddMulticastGroupSubAllowlistCliCommand { // Check requirements client.check_requirements(CHECK_ID_JSON | CHECK_BALANCE)?; - let pubkey = { - if self.pubkey.eq_ignore_ascii_case("me") { + let user_payer = { + if self.user_payer.eq_ignore_ascii_case("me") { client.get_payer() } else { - Pubkey::from_str(&self.pubkey)? + Pubkey::from_str(&self.user_payer)? } }; let signature = client.add_multicastgroup_sub_allowlist(AddMulticastGroupSubAllowlistCommand { pubkey_or_code: self.code, - pubkey, + client_ip: self.client_ip, + user_payer, })?; writeln!(out, "Signature: {signature}")?; @@ -65,6 +69,8 @@ mod tests { 100, 221, 20, 137, 4, 5, ]); + let client_ip = [100, 0, 0, 1].into(); + client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) @@ -73,7 +79,8 @@ mod tests { .expect_add_multicastgroup_sub_allowlist() .with(predicate::eq(AddMulticastGroupSubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip, + user_payer: pubkey, })) .returning(move |_| Ok(signature)); @@ -81,7 +88,8 @@ mod tests { let mut output = Vec::new(); let res = AddMulticastGroupSubAllowlistCliCommand { code: "test_code".to_string(), - pubkey: pubkey.to_string(), + client_ip, + user_payer: pubkey.to_string(), } .execute(&client, &mut output); assert!(res.is_ok()); diff --git a/smartcontract/cli/src/multicastgroup/allowlist/subscriber/list.rs b/smartcontract/cli/src/multicastgroup/allowlist/subscriber/list.rs index 0ca196ca1..f4ccf6ad3 100644 --- a/smartcontract/cli/src/multicastgroup/allowlist/subscriber/list.rs +++ b/smartcontract/cli/src/multicastgroup/allowlist/subscriber/list.rs @@ -1,11 +1,17 @@ use crate::doublezerocommand::CliCommand; +use ::serde::Serialize; use clap::Args; -use doublezero_sdk::commands::multicastgroup::allowlist::subscriber::list::ListMulticastGroupSubAllowlistCommand; -use std::io::Write; +use doublezero_program_common::serializer; +use doublezero_sdk::commands::{ + accesspass::list::ListAccessPassCommand, multicastgroup::get::GetMulticastGroupCommand, +}; +use solana_sdk::pubkey::Pubkey; +use std::{io::Write, net::Ipv4Addr}; +use tabled::{settings::Style, Table, Tabled}; #[derive(Args, Debug)] pub struct ListMulticastGroupSubAllowlistCliCommand { - /// Multicast group code or pubkey to list subscriber allowlist for + // Multicast group code or pubkey to list publisher allowlist for #[arg(long)] pub code: String, /// Output as pretty JSON @@ -16,33 +22,46 @@ pub struct ListMulticastGroupSubAllowlistCliCommand { pub json_compact: bool, } +#[derive(Tabled, Serialize)] +pub struct MulticastAllowlistDisplay { + #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] + pub account: Pubkey, + pub multicast_group: String, + pub client_ip: Ipv4Addr, + #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] + pub user_payer: Pubkey, +} + impl ListMulticastGroupSubAllowlistCliCommand { pub fn execute(self, client: &C, out: &mut W) -> eyre::Result<()> { - let list = - client.list_multicastgroup_sub_allowlist(ListMulticastGroupSubAllowlistCommand { - pubkey_or_code: self.code.clone(), - })?; - - if self.json || self.json_compact { - let list = list - .into_iter() - .map(|pubkey| pubkey.to_string()) - .collect::>(); - - let json = { - if self.json_compact { - serde_json::to_string(&list)? - } else { - serde_json::to_string_pretty(&list)? - } - }; - writeln!(out, "{json}")?; + let (mgroup_pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + pubkey_or_code: self.code.clone(), + })?; + + let list_accesspass = client.list_accesspass(ListAccessPassCommand {})?; + + let mga_displays = list_accesspass + .into_iter() + .filter(|(_, accesspass)| accesspass.mgroup_sub_allowlist.contains(&mgroup_pubkey)) + .map(|(_, accesspass)| MulticastAllowlistDisplay { + account: mgroup_pubkey, + multicast_group: mgroup.code.clone(), + client_ip: accesspass.client_ip, + user_payer: accesspass.user_payer, + }) + .collect::>(); + + let res = if self.json { + serde_json::to_string_pretty(&mga_displays)? + } else if self.json_compact { + serde_json::to_string(&mga_displays)? } else { - writeln!(out, "Pubkeys:")?; - for user in list { - writeln!(out, "\t{user}")?; - } - } + Table::new(mga_displays) + .with(Style::psql().remove_horizontals()) + .to_string() + }; + + writeln!(out, "{res}")?; Ok(()) } @@ -50,33 +69,93 @@ impl ListMulticastGroupSubAllowlistCliCommand { #[cfg(test)] mod tests { + use std::collections::HashMap; + use crate::{ multicastgroup::allowlist::subscriber::list::ListMulticastGroupSubAllowlistCliCommand, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, tests::utils::create_test_client, }; - use doublezero_sdk::commands::multicastgroup::allowlist::subscriber::list::ListMulticastGroupSubAllowlistCommand; + use doublezero_sdk::{ + commands::{ + accesspass::list::ListAccessPassCommand, multicastgroup::get::GetMulticastGroupCommand, + }, + AccountType, MulticastGroup, + }; + use doublezero_serviceability::state::accesspass::{ + AccessPass, AccessPassStatus, AccessPassType, + }; use mockall::predicate; use solana_sdk::pubkey::Pubkey; #[test] - fn test_cli_user_allowlist_list() { + fn test_cli_multicast_subscriber_allowlist_list() { let mut client = create_test_client(); - let pubkey1 = Pubkey::from_str_const("1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM"); - let pubkey2 = Pubkey::from_str_const("1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"); - let pubkey3 = Pubkey::from_str_const("11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3"); + let mgroup_pubkey = Pubkey::from_str_const("1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM"); + let mgroup = MulticastGroup { + account_type: AccountType::MulticastGroup, + index: 1, + bump_seed: 1, + code: "test".to_string(), + multicast_ip: [239, 0, 0, 1].into(), + max_bandwidth: 1000000000, + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), + tenant_pk: Pubkey::new_unique(), + status: doublezero_sdk::MulticastGroupStatus::Activated, + publisher_count: 5, + subscriber_count: 10, + }; + + let accesspass1_pk = Pubkey::from_str_const("1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"); + let accesspass1 = AccessPass { + account_type: AccountType::AccessPass, + bump_seed: 1, + client_ip: [100, 0, 0, 1].into(), + accesspass_type: AccessPassType::Prepaid, + last_access_epoch: 1234, + user_payer: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo8"), + mgroup_pub_allowlist: vec![mgroup_pubkey], + mgroup_sub_allowlist: vec![], + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo7"), + connection_count: 0, + status: AccessPassStatus::Requested, + }; + + let accesspass2_pk = Pubkey::from_str_const("11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3"); + let accesspass2 = AccessPass { + account_type: AccountType::AccessPass, + bump_seed: 1, + client_ip: [100, 0, 0, 1].into(), + accesspass_type: AccessPassType::Prepaid, + last_access_epoch: 1234, + user_payer: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo6"), + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![mgroup_pubkey], + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo5"), + connection_count: 0, + status: AccessPassStatus::Requested, + }; client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) .returning(|_| Ok(())); client - .expect_list_multicastgroup_sub_allowlist() - .with(predicate::eq(ListMulticastGroupSubAllowlistCommand { + .expect_get_multicastgroup() + .with(predicate::eq(GetMulticastGroupCommand { pubkey_or_code: "test".to_string(), })) - .returning(move |_| Ok(vec![pubkey1, pubkey2, pubkey3])); + .returning(move |_| Ok((mgroup_pubkey, mgroup.clone()))); + client + .expect_list_accesspass() + .with(predicate::eq(ListAccessPassCommand {})) + .returning(move |_| { + let mut list: HashMap = HashMap::new(); + list.insert(accesspass1_pk, accesspass1.clone()); + list.insert(accesspass2_pk, accesspass2.clone()); + Ok(list) + }); /*****************************************************************************************************/ let mut output = Vec::new(); @@ -89,7 +168,7 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); assert_eq!( - output_str,"Pubkeys:\n\t1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\n\t1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh\n\t11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3\n" + output_str," account | multicast_group | client_ip | user_payer \n 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM | test | 100.0.0.1 | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo6 \n" ); let mut output = Vec::new(); @@ -102,7 +181,7 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); assert_eq!( - output_str,"[\"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\",\"1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh\",\"11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3\"]\n" + output_str,"[{\"account\":\"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM\",\"multicast_group\":\"test\",\"client_ip\":\"100.0.0.1\",\"user_payer\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo6\"}]\n" ); } } diff --git a/smartcontract/cli/src/multicastgroup/allowlist/subscriber/remove.rs b/smartcontract/cli/src/multicastgroup/allowlist/subscriber/remove.rs index 0fcf17a4a..7d66d60bd 100644 --- a/smartcontract/cli/src/multicastgroup/allowlist/subscriber/remove.rs +++ b/smartcontract/cli/src/multicastgroup/allowlist/subscriber/remove.rs @@ -5,16 +5,19 @@ use crate::{ use clap::Args; use doublezero_sdk::commands::multicastgroup::allowlist::subscriber::remove::RemoveMulticastGroupSubAllowlistCommand; use solana_sdk::pubkey::Pubkey; -use std::{io::Write, str::FromStr}; +use std::{io::Write, net::Ipv4Addr, str::FromStr}; #[derive(Args, Debug)] pub struct RemoveMulticastGroupSubAllowlistCliCommand { /// Multicast group code or pubkey to remove subscriber allowlist for #[arg(long)] pub code: String, + /// Client IP address in IPv4 format + #[arg(long)] + pub client_ip: Ipv4Addr, /// Subscriber Pubkey or 'me' for current payer #[arg(long)] - pub pubkey: String, + pub user_payer: String, } impl RemoveMulticastGroupSubAllowlistCliCommand { @@ -22,18 +25,19 @@ impl RemoveMulticastGroupSubAllowlistCliCommand { // Check requirements client.check_requirements(CHECK_ID_JSON | CHECK_BALANCE)?; - let pubkey = { - if self.pubkey.eq_ignore_ascii_case("me") { + let user_payer = { + if self.user_payer.eq_ignore_ascii_case("me") { client.get_payer() } else { - Pubkey::from_str(&self.pubkey)? + Pubkey::from_str(&self.user_payer)? } }; let signature = client.remove_multicastgroup_sub_allowlist( RemoveMulticastGroupSubAllowlistCommand { pubkey_or_code: self.code, - pubkey, + client_ip: self.client_ip, + user_payer, }, )?; writeln!(out, "Signature: {signature}")?; @@ -65,6 +69,8 @@ mod tests { 100, 221, 20, 137, 4, 5, ]); + let client_ip = [100, 0, 0, 1].into(); + client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) @@ -73,7 +79,8 @@ mod tests { .expect_remove_multicastgroup_sub_allowlist() .with(predicate::eq(RemoveMulticastGroupSubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip, + user_payer: pubkey, })) .returning(move |_| Ok(signature)); @@ -81,7 +88,8 @@ mod tests { let mut output = Vec::new(); let res = RemoveMulticastGroupSubAllowlistCliCommand { code: "test_code".to_string(), - pubkey: pubkey.to_string(), + client_ip, + user_payer: pubkey.to_string(), } .execute(&client, &mut output); assert!(res.is_ok()); diff --git a/smartcontract/cli/src/multicastgroup/delete.rs b/smartcontract/cli/src/multicastgroup/delete.rs index 2215d55d4..18ed637a7 100644 --- a/smartcontract/cli/src/multicastgroup/delete.rs +++ b/smartcontract/cli/src/multicastgroup/delete.rs @@ -119,12 +119,10 @@ mod tests { tenant_pk: Pubkey::new_unique(), multicast_ip: [10, 0, 0, 1].into(), max_bandwidth: 1000000000, - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![], - subscribers: vec![], status: MulticastGroupStatus::Activated, owner: pda_pubkey, + publisher_count: 1, + subscriber_count: 0, }; client diff --git a/smartcontract/cli/src/multicastgroup/get.rs b/smartcontract/cli/src/multicastgroup/get.rs index a6559b9d2..fc1c94bd9 100644 --- a/smartcontract/cli/src/multicastgroup/get.rs +++ b/smartcontract/cli/src/multicastgroup/get.rs @@ -17,7 +17,7 @@ pub struct GetMulticastGroupCliCommand { impl GetMulticastGroupCliCommand { pub fn execute(self, client: &C, out: &mut W) -> eyre::Result<()> { - let (pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + let (mgroup_pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { pubkey_or_code: self.code, })?; @@ -27,15 +27,11 @@ impl GetMulticastGroupCliCommand { // Write the multicast group details first writeln!(out, - "account: {}\r\ncode: {}\r\nmulticast_ip: {}\r\nmax_bandwidth: {}\r\rpublisher_allowlist: {}\r\nsubscriber_allowlist: {}\r\npublishers: {}\r\nsubscribers: {}\r\nstatus: {}\r\nowner: {}\r\n\r\nusers:\r\n", - pubkey, + "account: {}\r\ncode: {}\r\nmulticast_ip: {}\r\nmax_bandwidth: {}\r\nstatus: {}\r\nowner: {}\r\n\r\nusers:\r\n", + mgroup_pubkey, mgroup.code, &mgroup.multicast_ip, bandwidth_to_string(&mgroup.max_bandwidth), - mgroup.pub_allowlist.iter().map(|p| p.to_string()).collect::>().join(", "), - mgroup.sub_allowlist.iter().map(|p| p.to_string()).collect::>().join(", "), - mgroup.publishers.iter().map(|p| p.to_string()).collect::>().join(", "), - mgroup.subscribers.iter().map(|p| p.to_string()).collect::>().join(", "), mgroup.status, mgroup.owner )?; @@ -55,11 +51,10 @@ impl GetMulticastGroupCliCommand { "owner", ]); - for (pubkey, data) in users - .into_iter() - .filter(|(pk, _)| mgroup.publishers.contains(pk) || mgroup.subscribers.contains(pk)) - { - let device = devices.get(&data.device_pk); + for (pubkey, user) in users.into_iter().filter(|(_, user)| { + user.publishers.contains(&mgroup_pubkey) || user.subscribers.contains(&mgroup_pubkey) + }) { + let device = devices.get(&user.device_pk); let location = match device { Some(device) => locations.get(&device.location_pk), None => None, @@ -67,7 +62,7 @@ impl GetMulticastGroupCliCommand { let device_name = match device { Some(device) => device.code.clone(), - None => data.device_pk.to_string(), + None => user.device_pk.to_string(), }; let location_name = match device { Some(device) => match location { @@ -76,16 +71,16 @@ impl GetMulticastGroupCliCommand { }, None => "".to_string(), }; - let mode_text = if mgroup.publishers.contains(&pubkey) { - if !mgroup.subscribers.contains(&pubkey) { - "Tx" + let mode_text = if user.publishers.contains(&mgroup_pubkey) { + if !user.subscribers.contains(&mgroup_pubkey) { + "P" } else { - "Tx/Rx" + "PS" } - } else if mgroup.subscribers.contains(&pubkey) { - "Rx" + } else if user.subscribers.contains(&mgroup_pubkey) { + "S" } else { - "XX" + "X" }; builder.push_record([ @@ -93,13 +88,13 @@ impl GetMulticastGroupCliCommand { mode_text, &device_name, &location_name, - &data.cyoa_type.to_string(), - data.client_ip.to_string().as_str(), - &data.tunnel_id.to_string(), - data.tunnel_net.to_string().as_str(), - data.dz_ip.to_string().as_str(), - &data.status.to_string(), - &data.owner.to_string(), + &user.cyoa_type.to_string(), + user.client_ip.to_string().as_str(), + &user.tunnel_id.to_string(), + user.tunnel_net.to_string().as_str(), + user.dz_ip.to_string().as_str(), + &user.status.to_string(), + &user.owner.to_string(), ]); } @@ -240,12 +235,10 @@ mod tests { tenant_pk: Pubkey::default(), multicast_ip: [10, 0, 0, 1].into(), max_bandwidth: 1000000000, - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![user1_pk], - subscribers: vec![], status: MulticastGroupStatus::Activated, owner: mgroup_pubkey, + publisher_count: 5, + subscriber_count: 10, }; client.expect_list_user().returning(move |_| { @@ -287,7 +280,7 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok(), "I should find a item by pubkey"); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\rpublisher_allowlist: \r\nsubscriber_allowlist: \r\npublishers: 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1\r\nsubscribers: \r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\n\r\nusers:\r\n\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | Tx | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); + assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\n\r\nusers:\r\n\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | P | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); // Expected success let mut output = Vec::new(); @@ -297,6 +290,6 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok(), "I should find a item by code"); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\rpublisher_allowlist: \r\nsubscriber_allowlist: \r\npublishers: 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1\r\nsubscribers: \r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\n\r\nusers:\r\n\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | Tx | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); + assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\n\r\nusers:\r\n\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | P | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); } } diff --git a/smartcontract/cli/src/multicastgroup/list.rs b/smartcontract/cli/src/multicastgroup/list.rs index 5afa4db05..aeac3681d 100644 --- a/smartcontract/cli/src/multicastgroup/list.rs +++ b/smartcontract/cli/src/multicastgroup/list.rs @@ -28,12 +28,8 @@ pub struct MulticastGroupDisplay { #[serde(serialize_with = "serializer::serialize_bandwidth_as_string")] #[tabled(display = "doublezero_program_common::types::parse_utils::bandwidth_to_string")] pub max_bandwidth: u64, - #[serde(serialize_with = "serializer::serialize_pubkeylist_as_string")] - #[tabled(display = "crate::util::display_count")] - pub publishers: Vec, - #[serde(serialize_with = "serializer::serialize_pubkeylist_as_string")] - #[tabled(display = "crate::util::display_count")] - pub subscribers: Vec, + pub publishers: u32, + pub subscribers: u32, pub status: MulticastGroupStatus, #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] pub owner: Pubkey, @@ -55,8 +51,8 @@ impl ListMulticastGroupCliCommand { owner: multicastgroup.owner, multicast_ip: multicastgroup.multicast_ip, max_bandwidth: multicastgroup.max_bandwidth, - publishers: multicastgroup.publishers, - subscribers: multicastgroup.subscribers, + publishers: multicastgroup.publisher_count, + subscribers: multicastgroup.subscriber_count, status: multicastgroup.status, }) .collect::>(); @@ -157,17 +153,10 @@ mod tests { code: "multicastgroup_code".to_string(), multicast_ip: [1, 2, 3, 4].into(), max_bandwidth: 1234, - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![ - Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo2"), - Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo3"), - ], - subscribers: vec![Pubkey::from_str_const( - "11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo3", - )], status: MulticastGroupStatus::Activated, owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), + publisher_count: 5, + subscriber_count: 10, }; client.expect_list_multicastgroup().returning(move |_| { @@ -185,7 +174,7 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, " account | code | multicast_ip | max_bandwidth | publishers | subscribers | status | owner \n 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPR | multicastgroup_code | 1.2.3.4 | 1.23Kbps | 2 | 1 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9 \n"); + assert_eq!(output_str, " account | code | multicast_ip | max_bandwidth | publishers | subscribers | status | owner \n 1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPR | multicastgroup_code | 1.2.3.4 | 1.23Kbps | 5 | 10 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9 \n"); let mut output = Vec::new(); let res = ListMulticastGroupCliCommand { @@ -196,6 +185,6 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "[{\"account\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPR\",\"code\":\"multicastgroup_code\",\"multicast_ip\":\"1.2.3.4\",\"max_bandwidth\":\"1.23Kbps\",\"publishers\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo2, 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo3\",\"subscribers\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo3\",\"status\":\"Activated\",\"owner\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9\"}]\n"); + assert_eq!(output_str, "[{\"account\":\"1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPR\",\"code\":\"multicastgroup_code\",\"multicast_ip\":\"1.2.3.4\",\"max_bandwidth\":\"1.23Kbps\",\"publishers\":5,\"subscribers\":10,\"status\":\"Activated\",\"owner\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9\"}]\n"); } } diff --git a/smartcontract/cli/src/multicastgroup/update.rs b/smartcontract/cli/src/multicastgroup/update.rs index a5e65a931..1408a3f30 100644 --- a/smartcontract/cli/src/multicastgroup/update.rs +++ b/smartcontract/cli/src/multicastgroup/update.rs @@ -24,6 +24,12 @@ pub struct UpdateMulticastGroupCliCommand { /// Updated maximum bandwidth (e.g. 1Gbps, 100Mbps) #[arg(long)] pub max_bandwidth: Option, + /// Updated publisher count + #[arg(long)] + pub publisher_count: Option, + /// Updated subscriber count + #[arg(long)] + pub subscriber_count: Option, /// Wait for the multicast group to be activated #[arg(short, long, default_value_t = false)] pub wait: bool, @@ -43,6 +49,8 @@ impl UpdateMulticastGroupCliCommand { code: self.code.clone(), multicast_ip: self.multicast_ip, max_bandwidth: self.max_bandwidth, + publisher_count: self.publisher_count, + subscriber_count: self.subscriber_count, })?; writeln!(out, "Signature: {signature}",)?; @@ -92,12 +100,10 @@ mod tests { tenant_pk: Pubkey::new_unique(), multicast_ip: [10, 0, 0, 1].into(), max_bandwidth: 1000000000, - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![], - subscribers: vec![], status: MulticastGroupStatus::Activated, owner: pda_pubkey, + publisher_count: 5, + subscriber_count: 10, }; client @@ -117,6 +123,8 @@ mod tests { code: Some("new_code".to_string()), multicast_ip: Some([10, 0, 0, 1].into()), max_bandwidth: Some(1000000000), + publisher_count: Some(5), + subscriber_count: Some(10), })) .returning(move |_| Ok(signature)); @@ -127,6 +135,8 @@ mod tests { code: Some("new_code".to_string()), multicast_ip: Some([10, 0, 0, 1].into()), max_bandwidth: Some(1000000000), + publisher_count: Some(5), + subscriber_count: Some(10), wait: false, } .execute(&client, &mut output); diff --git a/smartcontract/cli/src/user/create_subscribe.rs b/smartcontract/cli/src/user/create_subscribe.rs index ecfb1af42..64e471912 100644 --- a/smartcontract/cli/src/user/create_subscribe.rs +++ b/smartcontract/cli/src/user/create_subscribe.rs @@ -147,11 +147,9 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test".to_string(), - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![], - subscribers: vec![], owner: mgroup_pubkey, + publisher_count: 0, + subscriber_count: 0, }; let contributor_pk = Pubkey::from_str_const("HQ3UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx"); diff --git a/smartcontract/cli/src/user/get.rs b/smartcontract/cli/src/user/get.rs index 314cb48fe..f3728ab57 100644 --- a/smartcontract/cli/src/user/get.rs +++ b/smartcontract/cli/src/user/get.rs @@ -1,6 +1,9 @@ use crate::{doublezerocommand::CliCommand, validators::validate_pubkey}; use clap::Args; -use doublezero_sdk::commands::{accesspass::get::GetAccessPassCommand, user::get::GetUserCommand}; +use doublezero_sdk::commands::{ + accesspass::get::GetAccessPassCommand, multicastgroup::list::ListMulticastGroupCommand, + user::get::GetUserCommand, +}; use solana_sdk::pubkey::Pubkey; use std::{io::Write, str::FromStr}; @@ -20,6 +23,7 @@ impl GetUserCliCommand { client_ip: user.client_ip, user_payer: user.owner, })?; + let multicast_groups = client.list_multicastgroup(ListMulticastGroupCommand {})?; writeln!( out, @@ -45,12 +49,16 @@ impl GetUserCliCommand { accesspass, user.publishers .iter() - .map(|p| p.to_string()) + .map(|pk| multicast_groups + .get(pk) + .map_or(pk.to_string(), |mg| mg.code.clone())) .collect::>() .join(", "), user.subscribers .iter() - .map(|p| p.to_string()) + .map(|pk| multicast_groups + .get(pk) + .map_or(pk.to_string(), |mg| mg.code.clone())) .collect::>() .join(", "), user.status, @@ -72,7 +80,7 @@ mod tests { accesspass, user::{delete::DeleteUserCommand, get::GetUserCommand}, }, - AccountType, User, UserCYOA, UserStatus, UserType, + AccountType, MulticastGroup, User, UserCYOA, UserStatus, UserType, }; use doublezero_serviceability::{ pda::{get_accesspass_pda, get_user_pda}, @@ -93,6 +101,21 @@ mod tests { 100, 221, 20, 137, 4, 5, ]); + let mgroup_pubkey = Pubkey::new_unique(); + let mgroup = MulticastGroup { + account_type: AccountType::MulticastGroup, + owner: client.get_payer(), + bump_seed: 0, + index: 1, + code: "test".to_string(), + max_bandwidth: 1000, + status: doublezero_sdk::MulticastGroupStatus::Activated, + tenant_pk: Pubkey::default(), + multicast_ip: "100.0.0.1".parse().unwrap(), + publisher_count: 0, + subscriber_count: 1, + }; + let user = User { account_type: AccountType::User, index: 1, @@ -108,7 +131,7 @@ mod tests { status: UserStatus::Activated, owner: pda_pubkey, publishers: vec![], - subscribers: vec![], + subscribers: vec![mgroup_pubkey], validator_pubkey: Pubkey::default(), }; @@ -123,9 +146,21 @@ mod tests { last_access_epoch: 10, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], owner: client.get_payer(), }; + client + .expect_list_multicastgroup() + .with(predicate::eq( + doublezero_sdk::commands::multicastgroup::list::ListMulticastGroupCommand {}, + )) + .returning(move |_| { + let mut map = std::collections::HashMap::new(); + map.insert(mgroup_pubkey, mgroup.clone()); + Ok(map) + }); client .expect_get_accesspass() .with(predicate::eq(accesspass::get::GetAccessPassCommand { @@ -153,6 +188,6 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok(), "I should find a item by code"); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "account: CwpwPjV6LsVxHQ1Ye5bizyrXSa9j2Gk5C6y3WyMyYaA1\r\nuser_type: IBRL\r\ndevice: 11111111111111111111111111111111\r\ncyoa_type: GREOverDIA\r\nclient_ip: 10.0.0.1\r\ntunnel_net: 10.2.3.4/24\r\ndz_ip: 10.0.0.2\r\naccesspass: Prepaid: (expires epoch 10)\r\npublishers: \r\nsubscribers: \r\nstatus: activated\r\nowner: CwpwPjV6LsVxHQ1Ye5bizyrXSa9j2Gk5C6y3WyMyYaA1\n"); + assert_eq!(output_str, "account: CwpwPjV6LsVxHQ1Ye5bizyrXSa9j2Gk5C6y3WyMyYaA1\r\nuser_type: IBRL\r\ndevice: 11111111111111111111111111111111\r\ncyoa_type: GREOverDIA\r\nclient_ip: 10.0.0.1\r\ntunnel_net: 10.2.3.4/24\r\ndz_ip: 10.0.0.2\r\naccesspass: Prepaid: (expires epoch 10)\r\npublishers: \r\nsubscribers: test\r\nstatus: activated\r\nowner: CwpwPjV6LsVxHQ1Ye5bizyrXSa9j2Gk5C6y3WyMyYaA1\n"); } } diff --git a/smartcontract/cli/src/user/list.rs b/smartcontract/cli/src/user/list.rs index c31b185eb..ba9acba59 100644 --- a/smartcontract/cli/src/user/list.rs +++ b/smartcontract/cli/src/user/list.rs @@ -212,10 +212,10 @@ pub fn format_multicast_group_names( let mut result = name; if user.publishers.contains(pk) { - result.push_str(" (Tx)"); + result.insert_str(0, "P:"); } if user.subscribers.contains(pk) { - result.push_str(" (Rx)"); + result.insert_str(0, "S:"); } result }) @@ -366,12 +366,10 @@ mod tests { code: "m_code".to_string(), multicast_ip: [1, 2, 3, 4].into(), max_bandwidth: 1000, - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![], - subscribers: vec![user2_pubkey], status: MulticastGroupStatus::Activated, owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9"), + publisher_count: 0, + subscriber_count: 0, }; client.expect_list_location().returning(move |_| { @@ -431,6 +429,8 @@ mod tests { last_access_epoch: 10, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], owner: client.get_payer(), }; @@ -464,6 +464,9 @@ mod tests { last_access_epoch: 10, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![mgroup1_pubkey], + owner: client.get_payer(), }; @@ -494,7 +497,7 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, " account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner \n 11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo | Multicast | m_code (Rx) | device1_code | location1_name | GREOverDIA | 1.2.3.4 | 2.3.4.5 | Prepaid: (expires epoch 10) | 500 | 1.2.3.5/32 | activated | 11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo \n"); + assert_eq!(output_str, " account | user_type | groups | device | location | cyoa_type | client_ip | dz_ip | accesspass | tunnel_id | tunnel_net | status | owner \n 11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo | Multicast | S:m_code | device1_code | location1_name | GREOverDIA | 1.2.3.4 | 2.3.4.5 | Prepaid: (expires epoch 10) | 500 | 1.2.3.5/32 | activated | 11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo \n"); let mut output = Vec::new(); let res = ListUserCliCommand { @@ -508,6 +511,6 @@ mod tests { assert!(res.is_ok()); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "[{\"account\":\"11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo\",\"user_type\":\"Multicast\",\"device_pk\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9\",\"multicast\":\"m_code (Rx)\",\"publishers\":\"\",\"subscribers\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo8\",\"device_name\":\"device1_code\",\"location_code\":\"location1_code\",\"location_name\":\"location1_name\",\"cyoa_type\":\"GREOverDIA\",\"client_ip\":\"1.2.3.4\",\"dz_ip\":\"2.3.4.5\",\"accesspass\":\"Prepaid: (expires epoch 10)\",\"tunnel_id\":500,\"tunnel_net\":\"1.2.3.5/32\",\"status\":\"Activated\",\"owner\":\"11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo\"}]\n"); + assert_eq!(output_str, "[{\"account\":\"11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo\",\"user_type\":\"Multicast\",\"device_pk\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo9\",\"multicast\":\"S:m_code\",\"publishers\":\"\",\"subscribers\":\"11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo8\",\"device_name\":\"device1_code\",\"location_code\":\"location1_code\",\"location_name\":\"location1_name\",\"cyoa_type\":\"GREOverDIA\",\"client_ip\":\"1.2.3.4\",\"dz_ip\":\"2.3.4.5\",\"accesspass\":\"Prepaid: (expires epoch 10)\",\"tunnel_id\":500,\"tunnel_net\":\"1.2.3.5/32\",\"status\":\"Activated\",\"owner\":\"11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo\"}]\n"); } } diff --git a/smartcontract/cli/src/user/subscribe.rs b/smartcontract/cli/src/user/subscribe.rs index f57bdab71..7ea5e7f40 100644 --- a/smartcontract/cli/src/user/subscribe.rs +++ b/smartcontract/cli/src/user/subscribe.rs @@ -6,8 +6,9 @@ use crate::{ validators::{validate_pubkey, validate_pubkey_or_code}, }; use clap::Args; -use doublezero_sdk::commands::multicastgroup::{ - get::GetMulticastGroupCommand, subscribe::SubscribeMulticastGroupCommand, +use doublezero_sdk::commands::{ + multicastgroup::{get::GetMulticastGroupCommand, subscribe::SubscribeMulticastGroupCommand}, + user::get::GetUserCommand, }; use std::io::Write; @@ -35,8 +36,9 @@ impl SubscribeUserCliCommand { // Check requirements client.check_requirements(CHECK_ID_JSON | CHECK_BALANCE)?; - let user_pk = - parse_pubkey(&self.user).ok_or_else(|| eyre::eyre!("Invalid user: {}", self.user))?; + let (user_pk, user) = client.get_user(GetUserCommand { + pubkey: parse_pubkey(&self.user).ok_or_else(|| eyre::eyre!("Invalid user pubkey"))?, + })?; let group_pk = match parse_pubkey(&self.group) { Some(pk) => Some(pk), @@ -54,6 +56,7 @@ impl SubscribeUserCliCommand { let signature = client.subscribe_multicastgroup(SubscribeMulticastGroupCommand { user_pk, group_pk, + client_ip: user.client_ip, publisher: self.publisher, subscriber: self.subscriber, })?; @@ -82,17 +85,20 @@ mod tests { user::subscribe::SubscribeUserCliCommand, }; use doublezero_sdk::{ - commands::multicastgroup::{ - get::GetMulticastGroupCommand, subscribe::SubscribeMulticastGroupCommand, + commands::{ + multicastgroup::{ + get::GetMulticastGroupCommand, subscribe::SubscribeMulticastGroupCommand, + }, + user::get::GetUserCommand, }, - AccountType, MulticastGroup, MulticastGroupStatus, + AccountType, MulticastGroup, MulticastGroupStatus, User, UserCYOA, UserType, }; use doublezero_serviceability::pda::get_user_pda; use mockall::predicate; use solana_sdk::{pubkey::Pubkey, signature::Signature}; #[test] - fn test_cli_user_create_subscribe() { + fn test_cli_user_subscribe() { let mut client = create_test_client(); let (user_pubkey, _bump_seed) = get_user_pda(&client.get_program_id(), 1); @@ -102,6 +108,27 @@ mod tests { 139, 130, 217, 227, 214, 9, 242, 141, 223, 94, 29, 184, 110, 62, 32, 87, 137, 63, 139, 100, 221, 20, 137, 4, 5, ]); + + let client_ip = [192, 168, 1, 100].into(); + let user = User { + account_type: AccountType::User, + index: 1, + bump_seed: 255, + user_type: UserType::Multicast, + cyoa_type: UserCYOA::GREOverDIA, + device_pk: Pubkey::new_unique(), + owner: client.get_payer(), + tenant_pk: Pubkey::default(), + client_ip, + dz_ip: client_ip, + tunnel_id: 12345, + tunnel_net: "192.168.1.0/24".parse().unwrap(), + status: doublezero_sdk::UserStatus::Activated, + publishers: vec![], + subscribers: vec![], + validator_pubkey: Pubkey::default(), + }; + let mgroup_pubkey = Pubkey::from_str_const("11111115RidqCHAoz6dzmXxGcfWLNzevYqNpaRAUo"); let mgroup = MulticastGroup { account_type: AccountType::MulticastGroup, @@ -112,17 +139,21 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test".to_string(), - pub_allowlist: vec![], - sub_allowlist: vec![], - publishers: vec![], - subscribers: vec![], owner: mgroup_pubkey, + publisher_count: 0, + subscriber_count: 0, }; client .expect_check_requirements() .with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE)) .returning(|_| Ok(())); + client + .expect_get_user() + .with(predicate::eq(GetUserCommand { + pubkey: user_pubkey, + })) + .returning(move |_| Ok((user_pubkey, user.clone()))); client .expect_get_multicastgroup() .with(predicate::eq(GetMulticastGroupCommand { @@ -134,6 +165,7 @@ mod tests { .with(predicate::eq(SubscribeMulticastGroupCommand { user_pk: user_pubkey, group_pk: mgroup_pubkey, + client_ip, publisher: false, subscriber: true, })) diff --git a/smartcontract/programs/doublezero-serviceability/src/error.rs b/smartcontract/programs/doublezero-serviceability/src/error.rs index 46687852e..840cf2494 100644 --- a/smartcontract/programs/doublezero-serviceability/src/error.rs +++ b/smartcontract/programs/doublezero-serviceability/src/error.rs @@ -109,6 +109,8 @@ pub enum DoubleZeroError { InvalidMulticastIp, // variant 51 #[error("Invalid Account Owner")] InvalidAccountOwner, // variant 52 + #[error("Access Pass not found")] + AccessPassNotFound, // variant 53 } impl From for ProgramError { @@ -167,6 +169,7 @@ impl From for ProgramError { DoubleZeroError::InvalidMaxBandwidth => ProgramError::Custom(50), DoubleZeroError::InvalidMulticastIp => ProgramError::Custom(51), DoubleZeroError::InvalidAccountOwner => ProgramError::Custom(52), + DoubleZeroError::AccessPassNotFound => ProgramError::Custom(53), } } } @@ -226,6 +229,7 @@ impl From for DoubleZeroError { 50 => DoubleZeroError::InvalidMaxBandwidth, 51 => DoubleZeroError::InvalidMulticastIp, 52 => DoubleZeroError::InvalidAccountOwner, + 53 => DoubleZeroError::AccessPassNotFound, _ => DoubleZeroError::Custom(e), } diff --git a/smartcontract/programs/doublezero-serviceability/src/helper.rs b/smartcontract/programs/doublezero-serviceability/src/helper.rs index b114c689c..6bc81065e 100644 --- a/smartcontract/programs/doublezero-serviceability/src/helper.rs +++ b/smartcontract/programs/doublezero-serviceability/src/helper.rs @@ -163,6 +163,7 @@ macro_rules! format_option { pub fn deserialize_vec_with_capacity( data: &mut &[u8], ) -> Result, ProgramError> { + // If the data doesn't contain enough bytes to read the vector size (4 bytes), return an empty vector. let len = u32::from_le_bytes(match data.get(..4) { Some(bytes) => match bytes.try_into() { Ok(arr) => arr, @@ -187,3 +188,32 @@ pub fn is_global(ip: Ipv4Addr) -> bool { && !ip.is_documentation() && !ip.is_unspecified() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_vec_with_capacity() { + // Normal case + let data = [3u8, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30, 0, 0, 0]; + let result = deserialize_vec_with_capacity::(&mut &data[..]).unwrap(); + assert_eq!(result, vec![10, 20, 30]); + + // Error case: not enough data to read length + let data = [0u8]; // Incomplete length + let err = deserialize_vec_with_capacity::(&mut &data[..]).unwrap(); + assert_eq!(err, Vec::::new()); + } + + #[test] + fn test_is_global() { + assert!(is_global(Ipv4Addr::new(8, 8, 8, 8))); // Public IP + assert!(!is_global(Ipv4Addr::new(10, 0, 0, 1))); // Private IP + assert!(!is_global(Ipv4Addr::new(127, 0, 0, 1))); // Loopback IP + assert!(!is_global(Ipv4Addr::new(169, 254, 0, 1))); // Link-local IP + assert!(!is_global(Ipv4Addr::new(255, 255, 255, 255))); // Broadcast IP + assert!(!is_global(Ipv4Addr::new(192, 0, 2, 1))); // Documentation IP + assert!(!is_global(Ipv4Addr::new(0, 0, 0, 0))); // Unspecified IP + } +} diff --git a/smartcontract/programs/doublezero-serviceability/src/instructions.rs b/smartcontract/programs/doublezero-serviceability/src/instructions.rs index 9e38aab46..ec8a1f06d 100644 --- a/smartcontract/programs/doublezero-serviceability/src/instructions.rs +++ b/smartcontract/programs/doublezero-serviceability/src/instructions.rs @@ -807,6 +807,8 @@ mod tests { multicast_ip: Some([1, 2, 3, 4].into()), max_bandwidth: Some(1000), code: Some("test".to_string()), + publisher_count: None, + subscriber_count: None, }), "UpdateMulticastGroup", ); @@ -834,7 +836,8 @@ mod tests { test_instruction( DoubleZeroInstruction::AddMulticastGroupPubAllowlist( AddMulticastGroupPubAllowlistArgs { - pubkey: Pubkey::new_unique(), + client_ip: [1, 2, 3, 4].into(), + user_payer: Pubkey::new_unique(), }, ), "AddMulticastGroupPubAllowlist", @@ -842,7 +845,8 @@ mod tests { test_instruction( DoubleZeroInstruction::RemoveMulticastGroupPubAllowlist( RemoveMulticastGroupPubAllowlistArgs { - pubkey: Pubkey::new_unique(), + client_ip: [1, 2, 3, 4].into(), + user_payer: Pubkey::new_unique(), }, ), "RemoveMulticastGroupPubAllowlist", @@ -850,7 +854,8 @@ mod tests { test_instruction( DoubleZeroInstruction::AddMulticastGroupSubAllowlist( AddMulticastGroupSubAllowlistArgs { - pubkey: Pubkey::new_unique(), + client_ip: [1, 2, 3, 4].into(), + user_payer: Pubkey::new_unique(), }, ), "AddMulticastGroupSubAllowlist", @@ -858,13 +863,15 @@ mod tests { test_instruction( DoubleZeroInstruction::RemoveMulticastGroupSubAllowlist( RemoveMulticastGroupSubAllowlistArgs { - pubkey: Pubkey::new_unique(), + client_ip: [1, 2, 3, 4].into(), + user_payer: Pubkey::new_unique(), }, ), "RemoveMulticastGroupSubAllowlist", ); test_instruction( DoubleZeroInstruction::SubscribeMulticastGroup(MulticastGroupSubscribeArgs { + client_ip: [1, 2, 3, 4].into(), publisher: false, subscriber: true, }), diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/check_status.rs b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/check_status.rs index b44bc318b..269e22bd1 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/check_status.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/check_status.rs @@ -32,6 +32,13 @@ pub fn process_check_status_access_pass( #[cfg(test)] msg!("process_check_status_access_pass({:?})", _value); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid AccessPass Account Owner" + ); // Check the owner of the accounts assert_eq!( *globalstate_account.owner, diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs index 2e9662414..7f7869171 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/close.rs @@ -35,6 +35,14 @@ pub fn process_close_access_pass( #[cfg(test)] msg!("process_close_accesspass({:?})", _value); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid AccessPass Account Owner" + ); + // Check the owner of the accounts assert_eq!( *globalstate_account.owner, diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs index 4972e6263..576e85fe6 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/accesspass/set.rs @@ -125,6 +125,8 @@ pub fn process_set_access_pass( connection_count: 0, status: AccessPassStatus::Requested, owner: *payer_account.key, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], }; try_create_account( @@ -165,6 +167,8 @@ pub fn process_set_access_pass( connection_count: 0, status: AccessPassStatus::Requested, owner: *payer_account.key, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], } }; @@ -177,7 +181,6 @@ pub fn process_set_access_pass( accounts, accesspass.size(), )?; - accesspass.try_serialize(accesspass_account)?; #[cfg(test)] @@ -187,8 +190,9 @@ pub fn process_set_access_pass( let deposit = AIRDROP_USER_RENT_LAMPORTS .saturating_add(globalstate.user_airdrop_lamports) .saturating_sub(user_payer.lamports()); + if deposit != 0 { - msg!("Airdropping {} lamports to user", deposit); + msg!("Airdropping {} lamports to user account", deposit); invoke_signed_unchecked( &system_instruction::transfer(payer_account.key, user_payer.key, deposit), &[ diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/add.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/add.rs index f2b1f54cc..4c3ddb1c6 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/add.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/add.rs @@ -1,7 +1,16 @@ -use core::fmt; - -use crate::{error::DoubleZeroError, helper::account_write, state::multicastgroup::MulticastGroup}; +use crate::{ + error::DoubleZeroError, + pda::get_accesspass_pda, + seeds::{SEED_ACCESS_PASS, SEED_PREFIX}, + state::{ + accesspass::{AccessPass, AccessPassStatus, AccessPassType}, + accounttype::{AccountType, AccountTypeInfo}, + multicastgroup::MulticastGroup, + }, +}; use borsh::{BorshDeserialize, BorshSerialize}; +use core::fmt; +use doublezero_program_common::{resize_account::resize_account_if_needed, try_create_account}; #[cfg(test)] use solana_program::msg; use solana_program::{ @@ -9,15 +18,21 @@ use solana_program::{ entrypoint::ProgramResult, pubkey::Pubkey, }; +use std::net::Ipv4Addr; -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, Default)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)] pub struct AddMulticastGroupPubAllowlistArgs { - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl fmt::Debug for AddMulticastGroupPubAllowlistArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "pubkey: {}", self.pubkey) + write!( + f, + "client_ip: {}, user_payer: {}", + self.client_ip, self.user_payer + ) } } @@ -29,11 +44,12 @@ pub fn process_add_multicastgroup_pub_allowlist( let accounts_iter = &mut accounts.iter(); let mgroup_account = next_account_info(accounts_iter)?; + let accesspass_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; #[cfg(test)] - msg!("process_add_user_allowlist({:?})", value); + msg!("process_add_multicastgroup_pub_allowlist({:?})", value); // Check the owner of the accounts assert_eq!( @@ -49,16 +65,76 @@ pub fn process_add_multicastgroup_pub_allowlist( assert!(mgroup_account.is_writable, "PDA Account is not writable"); // Parse the global state account & check if the payer is in the allowlist - let mut mgroup = MulticastGroup::try_from(mgroup_account)?; + let mgroup = MulticastGroup::try_from(mgroup_account)?; if mgroup.owner != *payer_account.key { return Err(DoubleZeroError::NotAllowed.into()); } - if !mgroup.pub_allowlist.contains(&value.pubkey) { - mgroup.pub_allowlist.push(value.pubkey); - } + if accesspass_account.data_is_empty() { + let (expected_pda_account, bump_seed) = + get_accesspass_pda(program_id, &value.client_ip, &value.user_payer); + assert_eq!( + accesspass_account.key, &expected_pda_account, + "Invalid AccessPass PubKey" + ); + let accesspass = AccessPass { + account_type: AccountType::AccessPass, + bump_seed, + accesspass_type: AccessPassType::Prepaid, + client_ip: value.client_ip, + user_payer: value.user_payer, + last_access_epoch: 0, + connection_count: 0, + status: AccessPassStatus::Requested, + owner: *payer_account.key, + mgroup_pub_allowlist: vec![*mgroup_account.key], + mgroup_sub_allowlist: vec![], + }; - account_write(mgroup_account, &mgroup, payer_account, system_program)?; + try_create_account( + payer_account.key, // Account paying for the new account + accesspass_account.key, // Account to be created + accesspass_account.lamports(), // Current amount of lamports on the new account + accesspass.size(), // Size in bytes to allocate for the data field + program_id, // Set program owner to our program + accounts, + &[ + SEED_PREFIX, + SEED_ACCESS_PASS, + &value.client_ip.octets(), + &value.user_payer.to_bytes(), + &[bump_seed], + ], + )?; + accesspass.try_serialize(accesspass_account)?; + } else { + assert_eq!( + accesspass_account.owner, program_id, + "Invalid Accesspass Account Owner" + ); + + let mut accesspass = AccessPass::try_from(accesspass_account)?; + assert!( + accesspass.client_ip == value.client_ip, + "AccessPass client_ip does not match" + ); + assert!( + accesspass.user_payer == value.user_payer, + "AccessPass user_payer does not match" + ); + + if !accesspass.mgroup_pub_allowlist.contains(mgroup_account.key) { + accesspass.mgroup_pub_allowlist.push(*mgroup_account.key); + } + + resize_account_if_needed( + accesspass_account, + payer_account, + accounts, + accesspass.size(), + )?; + accesspass.try_serialize(accesspass_account)?; + } #[cfg(test)] msg!("Updated: {:?}", mgroup); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/remove.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/remove.rs index 4a60bfce0..472ea68f7 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/remove.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/publisher/remove.rs @@ -1,6 +1,10 @@ -use crate::{error::DoubleZeroError, helper::account_write, state::multicastgroup::MulticastGroup}; +use crate::{ + error::DoubleZeroError, + state::{accesspass::AccessPass, accounttype::AccountTypeInfo, multicastgroup::MulticastGroup}, +}; use borsh::{BorshDeserialize, BorshSerialize}; use core::fmt; +use doublezero_program_common::resize_account::resize_account_if_needed; #[cfg(test)] use solana_program::msg; use solana_program::{ @@ -8,15 +12,21 @@ use solana_program::{ entrypoint::ProgramResult, pubkey::Pubkey, }; +use std::net::Ipv4Addr; -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, Default)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)] pub struct RemoveMulticastGroupPubAllowlistArgs { - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl fmt::Debug for RemoveMulticastGroupPubAllowlistArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "pubkey: {}", self.pubkey) + write!( + f, + "client_ip: {}, user_payer: {}", + self.client_ip, self.user_payer + ) } } @@ -28,17 +38,25 @@ pub fn process_remove_multicast_pub_allowlist( let accounts_iter = &mut accounts.iter(); let mgroup_account = next_account_info(accounts_iter)?; + let accesspass_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; #[cfg(test)] - msg!("process_add_user_allowlist({:?})", value); + msg!("process_remove_multicast_pub_allowlist({:?})", value); // Check the owner of the accounts assert_eq!( mgroup_account.owner, program_id, "Invalid PDA Account Owner" ); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid Accesspass Account Owner" + ); assert_eq!( *system_program.unsigned_key(), solana_program::system_program::id(), @@ -48,14 +66,32 @@ pub fn process_remove_multicast_pub_allowlist( assert!(mgroup_account.is_writable, "PDA Account is not writable"); // Parse the global state account & check if the payer is in the allowlist - let mut mgroup = MulticastGroup::try_from(mgroup_account)?; + let mgroup = MulticastGroup::try_from(mgroup_account)?; if mgroup.owner != *payer_account.key { return Err(DoubleZeroError::NotAllowed.into()); } - mgroup.pub_allowlist.retain(|x| x != &value.pubkey); + let mut accesspass = AccessPass::try_from(accesspass_account)?; + assert!( + accesspass.client_ip == value.client_ip, + "AccessPass client_ip does not match" + ); + assert!( + accesspass.user_payer == value.user_payer, + "AccessPass user_payer does not match" + ); + + accesspass + .mgroup_pub_allowlist + .retain(|x| x != mgroup_account.key); - account_write(mgroup_account, &mgroup, payer_account, system_program)?; + resize_account_if_needed( + accesspass_account, + payer_account, + accounts, + accesspass.size(), + )?; + accesspass.try_serialize(accesspass_account)?; #[cfg(test)] msg!("Updated: {:?}", mgroup); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/add.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/add.rs index f5bff2bec..51b830a52 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/add.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/add.rs @@ -1,7 +1,16 @@ -use core::fmt; - -use crate::{error::DoubleZeroError, helper::account_write, state::multicastgroup::MulticastGroup}; +use crate::{ + error::DoubleZeroError, + pda::get_accesspass_pda, + seeds::{SEED_ACCESS_PASS, SEED_PREFIX}, + state::{ + accesspass::{AccessPass, AccessPassStatus, AccessPassType}, + accounttype::{AccountType, AccountTypeInfo}, + multicastgroup::MulticastGroup, + }, +}; use borsh::{BorshDeserialize, BorshSerialize}; +use core::fmt; +use doublezero_program_common::{resize_account::resize_account_if_needed, try_create_account}; #[cfg(test)] use solana_program::msg; use solana_program::{ @@ -9,15 +18,21 @@ use solana_program::{ entrypoint::ProgramResult, pubkey::Pubkey, }; +use std::net::Ipv4Addr; -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, Default)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)] pub struct AddMulticastGroupSubAllowlistArgs { - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl fmt::Debug for AddMulticastGroupSubAllowlistArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "pubkey: {}", self.pubkey) + write!( + f, + "client_ip: {}, user_payer: {}", + self.client_ip, self.user_payer + ) } } @@ -29,11 +44,12 @@ pub fn process_add_multicastgroup_sub_allowlist( let accounts_iter = &mut accounts.iter(); let mgroup_account = next_account_info(accounts_iter)?; + let accesspass_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; #[cfg(test)] - msg!("process_add_user_allowlist({:?})", value); + msg!("process_add_multicastgroup_sub_allowlist({:?})", value); // Check the owner of the accounts assert_eq!( @@ -49,16 +65,76 @@ pub fn process_add_multicastgroup_sub_allowlist( assert!(mgroup_account.is_writable, "PDA Account is not writable"); // Parse the global state account & check if the payer is in the allowlist - let mut mgroup = MulticastGroup::try_from(mgroup_account)?; + let mgroup = MulticastGroup::try_from(mgroup_account)?; if mgroup.owner != *payer_account.key { return Err(DoubleZeroError::NotAllowed.into()); } - if !mgroup.sub_allowlist.contains(&value.pubkey) { - mgroup.sub_allowlist.push(value.pubkey); - } + if accesspass_account.data_is_empty() { + let (expected_pda_account, bump_seed) = + get_accesspass_pda(program_id, &value.client_ip, &value.user_payer); + assert_eq!( + accesspass_account.key, &expected_pda_account, + "Invalid AccessPass PubKey" + ); + let accesspass = AccessPass { + account_type: AccountType::AccessPass, + bump_seed, + accesspass_type: AccessPassType::Prepaid, + client_ip: value.client_ip, + user_payer: value.user_payer, + last_access_epoch: 0, + connection_count: 0, + status: AccessPassStatus::Requested, + owner: *payer_account.key, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![*mgroup_account.key], + }; - account_write(mgroup_account, &mgroup, payer_account, system_program)?; + try_create_account( + payer_account.key, // Account paying for the new account + accesspass_account.key, // Account to be created + accesspass_account.lamports(), // Current amount of lamports on the new account + accesspass.size(), // Size in bytes to allocate for the data field + program_id, // Set program owner to our program + accounts, + &[ + SEED_PREFIX, + SEED_ACCESS_PASS, + &value.client_ip.octets(), + &value.user_payer.to_bytes(), + &[bump_seed], + ], + )?; + accesspass.try_serialize(accesspass_account)?; + } else { + assert_eq!( + accesspass_account.owner, program_id, + "Invalid Accesspass Account Owner" + ); + + let mut accesspass = AccessPass::try_from(accesspass_account)?; + assert!( + accesspass.client_ip == value.client_ip, + "AccessPass client_ip does not match" + ); + assert!( + accesspass.user_payer == value.user_payer, + "AccessPass user_payer does not match" + ); + + if !accesspass.mgroup_sub_allowlist.contains(mgroup_account.key) { + accesspass.mgroup_sub_allowlist.push(*mgroup_account.key); + } + + resize_account_if_needed( + accesspass_account, + payer_account, + accounts, + accesspass.size(), + )?; + accesspass.try_serialize(accesspass_account)?; + } #[cfg(test)] msg!("Updated: {:?}", mgroup); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/remove.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/remove.rs index 5ce233f3f..2408b7002 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/remove.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/allowlist/subscriber/remove.rs @@ -1,6 +1,10 @@ -use crate::{error::DoubleZeroError, helper::account_write, state::multicastgroup::MulticastGroup}; +use crate::{ + error::DoubleZeroError, + state::{accesspass::AccessPass, accounttype::AccountTypeInfo, multicastgroup::MulticastGroup}, +}; use borsh::{BorshDeserialize, BorshSerialize}; use core::fmt; +use doublezero_program_common::resize_account::resize_account_if_needed; #[cfg(test)] use solana_program::msg; use solana_program::{ @@ -8,15 +12,21 @@ use solana_program::{ entrypoint::ProgramResult, pubkey::Pubkey, }; +use std::net::Ipv4Addr; -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, Default)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)] pub struct RemoveMulticastGroupSubAllowlistArgs { - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl fmt::Debug for RemoveMulticastGroupSubAllowlistArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "pubkey: {}", self.pubkey) + write!( + f, + "client_ip: {}, user_payer: {}", + self.client_ip, self.user_payer + ) } } @@ -28,17 +38,25 @@ pub fn process_remove_multicast_sub_allowlist( let accounts_iter = &mut accounts.iter(); let mgroup_account = next_account_info(accounts_iter)?; + let accesspass_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; #[cfg(test)] - msg!("process_add_user_allowlist({:?})", value); + msg!("process_remove_multicast_sub_allowlist({:?})", value); // Check the owner of the accounts assert_eq!( mgroup_account.owner, program_id, "Invalid PDA Account Owner" ); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid Accesspass Account Owner" + ); assert_eq!( *system_program.unsigned_key(), solana_program::system_program::id(), @@ -48,14 +66,32 @@ pub fn process_remove_multicast_sub_allowlist( assert!(mgroup_account.is_writable, "PDA Account is not writable"); // Parse the global state account & check if the payer is in the allowlist - let mut mgroup = MulticastGroup::try_from(mgroup_account)?; + let mgroup = MulticastGroup::try_from(mgroup_account)?; if mgroup.owner != *payer_account.key { return Err(DoubleZeroError::NotAllowed.into()); } - mgroup.sub_allowlist.retain(|x| x != &value.pubkey); + let mut accesspass = AccessPass::try_from(accesspass_account)?; + assert!( + accesspass.client_ip == value.client_ip, + "AccessPass client_ip does not match" + ); + assert!( + accesspass.user_payer == value.user_payer, + "AccessPass user_payer does not match" + ); + + accesspass + .mgroup_sub_allowlist + .retain(|x| x != mgroup_account.key); - account_write(mgroup_account, &mgroup, payer_account, system_program)?; + resize_account_if_needed( + accesspass_account, + payer_account, + accounts, + accesspass.size(), + )?; + accesspass.try_serialize(accesspass_account)?; #[cfg(test)] msg!("Updated: {:?}", mgroup); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/create.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/create.rs index 4c6950a08..8c2172c57 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/create.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/create.rs @@ -98,11 +98,9 @@ pub fn process_create_multicastgroup( code, multicast_ip: std::net::Ipv4Addr::UNSPECIFIED, max_bandwidth: value.max_bandwidth, - pub_allowlist: vec![], - sub_allowlist: vec![], - subscribers: vec![], - publishers: vec![], status: MulticastGroupStatus::Pending, + publisher_count: 0, + subscriber_count: 0, }; account_create( diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/subscribe.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/subscribe.rs index 7e0fa6d51..e778acc96 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/subscribe.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/subscribe.rs @@ -1,22 +1,24 @@ use crate::{ error::DoubleZeroError, helper::account_write, + pda::get_accesspass_pda, state::{ + accesspass::AccessPass, multicastgroup::{MulticastGroup, MulticastGroupStatus}, user::{User, UserStatus}, }, }; use borsh::{BorshDeserialize, BorshSerialize}; -#[cfg(test)] -use solana_program::msg; use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, + msg, pubkey::Pubkey, }; -use std::fmt; -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone, Default)] +use std::{fmt, net::Ipv4Addr}; +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)] pub struct MulticastGroupSubscribeArgs { + pub client_ip: Ipv4Addr, pub publisher: bool, pub subscriber: bool, } @@ -25,8 +27,8 @@ impl fmt::Debug for MulticastGroupSubscribeArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "publisher: {:?}, subscriber: {:?}", - self.publisher, self.subscriber + "client_ip: {}, publisher: {:?}, subscriber: {:?}", + self.client_ip, self.publisher, self.subscriber ) } } @@ -38,9 +40,9 @@ pub fn process_subscribe_multicastgroup( ) -> ProgramResult { let accounts_iter = &mut accounts.iter(); - let multicastgroup_account = next_account_info(accounts_iter)?; + let mgroup_account = next_account_info(accounts_iter)?; + let accesspass_account = next_account_info(accounts_iter)?; let user_account = next_account_info(accounts_iter)?; - let globalstate_account = next_account_info(accounts_iter)?; let payer_account = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; @@ -49,30 +51,30 @@ pub fn process_subscribe_multicastgroup( // Check the owner of the accounts assert_eq!( - multicastgroup_account.owner, program_id, + mgroup_account.owner, program_id, "Invalid PDA Account Owner" ); - assert_eq!(user_account.owner, program_id, "Invalid PDA Account Owner"); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } assert_eq!( - globalstate_account.owner, program_id, - "Invalid GlobalState Account Owner" + accesspass_account.owner, program_id, + "Invalid Accesspass Account Owner" ); + assert_eq!(user_account.owner, program_id, "Invalid PDA Account Owner"); assert_eq!( *system_program.unsigned_key(), solana_program::system_program::id(), "Invalid System Program Account Owner" ); assert!( - multicastgroup_account.is_writable, + mgroup_account.is_writable, "multicastgroup account is not writable" ); assert!(user_account.is_writable, "user account is not writable"); - // Parse the global state account & check if the payer is in the allowlist - //let _globalstate = globalstate_get(globalstate_account)?; // Parse accounts - let mut mgroup: MulticastGroup = MulticastGroup::try_from(multicastgroup_account)?; - + let mut mgroup: MulticastGroup = MulticastGroup::try_from(mgroup_account)?; if mgroup.status != MulticastGroupStatus::Activated { #[cfg(test)] msg!("MulticastGroupStatus: {:?}", mgroup.status); @@ -81,64 +83,76 @@ pub fn process_subscribe_multicastgroup( } let mut user: User = User::try_from(user_account)?; - if user.status != UserStatus::Activated && user.status != UserStatus::Updating { - #[cfg(test)] msg!("UserStatus: {:?}", user.status); - return Err(DoubleZeroError::InvalidStatus.into()); } + let (accesspass_pda, _) = get_accesspass_pda(program_id, &value.client_ip, payer_account.key); + assert_eq!( + accesspass_account.key, &accesspass_pda, + "Invalid AccessPass PDA" + ); + + let accesspass = AccessPass::try_from(accesspass_account)?; + assert!( + accesspass.client_ip == user.client_ip, + "AccessPass client_ip does not match" + ); + // Check if the user is in the allowlist - if value.publisher && !mgroup.pub_allowlist.contains(payer_account.key) { + if value.publisher && !accesspass.mgroup_pub_allowlist.contains(mgroup_account.key) { + msg!("{:?}", accesspass); return Err(DoubleZeroError::NotAllowed.into()); } - if value.subscriber && !mgroup.sub_allowlist.contains(payer_account.key) { + if value.subscriber && !accesspass.mgroup_sub_allowlist.contains(mgroup_account.key) { + msg!("{:?}", accesspass); return Err(DoubleZeroError::NotAllowed.into()); } + // Manage the publisher lists match value.publisher { true => { - if !user.publishers.contains(multicastgroup_account.key) { - user.publishers.push(*multicastgroup_account.key); + if !user.publishers.contains(mgroup_account.key) { + // Increment publisher count + mgroup.publisher_count = mgroup.publisher_count.saturating_add(1); + // Add multicast group to user's publisher list + user.publishers.push(*mgroup_account.key); user.status = UserStatus::Updating; } - - if !mgroup.publishers.contains(user_account.key) { - mgroup.publishers.push(*user_account.key); - } } false => { - user.publishers - .retain(|&x| x != *multicastgroup_account.key); - mgroup.publishers.retain(|&x| x != *user_account.key); + if user.publishers.contains(mgroup_account.key) { + // Decrement publisher count + mgroup.publisher_count = mgroup.publisher_count.saturating_sub(1); + // Remove multicast group from user's publisher list + user.publishers.retain(|&x| x != *mgroup_account.key); + } } } + // Manage the subscriber lists match value.subscriber { true => { - if !user.subscribers.contains(multicastgroup_account.key) { - user.subscribers.push(*multicastgroup_account.key); + if !user.subscribers.contains(mgroup_account.key) { + // Increment subscriber count + mgroup.subscriber_count = mgroup.subscriber_count.saturating_add(1); + // Add multicast group to user's subscriber list + user.subscribers.push(*mgroup_account.key); user.status = UserStatus::Updating; } - - if !mgroup.subscribers.contains(user_account.key) { - mgroup.subscribers.push(*user_account.key); - } } false => { - user.subscribers - .retain(|&x| x != *multicastgroup_account.key); - mgroup.subscribers.retain(|&x| x != *user_account.key); + if user.subscribers.contains(mgroup_account.key) { + // Decrement subscriber count + mgroup.subscriber_count = mgroup.subscriber_count.saturating_sub(1); + // Remove multicast group from user's subscriber list + user.subscribers.retain(|&x| x != *mgroup_account.key); + } } } - account_write( - multicastgroup_account, - &mgroup, - payer_account, - system_program, - )?; + account_write(mgroup_account, &mgroup, payer_account, system_program)?; account_write(user_account, &user, payer_account, system_program)?; #[cfg(test)] diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/update.rs b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/update.rs index 910745e8b..d866dd5a2 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/update.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/update.rs @@ -16,6 +16,8 @@ pub struct MulticastGroupUpdateArgs { pub code: Option, pub multicast_ip: Option, pub max_bandwidth: Option, + pub publisher_count: Option, + pub subscriber_count: Option, } impl fmt::Debug for MulticastGroupUpdateArgs { @@ -80,6 +82,12 @@ pub fn process_update_multicastgroup( if let Some(ref max_bandwidth) = value.max_bandwidth { multicastgroup.max_bandwidth = *max_bandwidth; } + if let Some(ref publisher_count) = value.publisher_count { + multicastgroup.publisher_count = *publisher_count; + } + if let Some(ref subscriber_count) = value.subscriber_count { + multicastgroup.subscriber_count = *subscriber_count; + } account_write( multicastgroup_account, diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/activate.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/activate.rs index 692b1c9fa..65ae7a897 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/activate.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/activate.rs @@ -54,6 +54,9 @@ pub fn process_activate_user( // Check the owner of the accounts assert_eq!(user_account.owner, program_id, "Invalid User Account Owner"); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } assert_eq!( accesspass_account.owner, program_id, "Invalid AccessPass Account Owner" diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/check_access_pass.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/check_access_pass.rs index e8e970854..2e0833944 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/check_access_pass.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/check_access_pass.rs @@ -44,6 +44,14 @@ pub fn process_check_access_pass_user( // Check the owner of the accounts assert_eq!(user_account.owner, program_id, "Invalid PDA Account Owner"); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid AccessPass Account Owner" + ); + assert_eq!( globalstate_account.owner, program_id, "Invalid GlobalState Account Owner" diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/create.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/create.rs index a60f8085e..59bbbf616 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/create.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/create.rs @@ -60,6 +60,14 @@ pub fn process_create_user( if !user_account.data.borrow().is_empty() { return Err(ProgramError::AccountAlreadyInitialized); } + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid AccessPass Account Owner" + ); + let globalstate = globalstate_get_next(globalstate_account)?; let (expected_pda_account, bump_seed) = get_user_pda(program_id, globalstate.account_index); diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs index 808eda232..5934139fd 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs @@ -64,6 +64,14 @@ pub fn process_create_subscribe_user( if !user_account.data.borrow().is_empty() { return Err(ProgramError::AccountAlreadyInitialized); } + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid AccessPass Account Owner" + ); + let globalstate = globalstate_get_next(globalstate_account)?; let (expected_pda_account, bump_seed) = get_user_pda(program_id, globalstate.account_index); @@ -131,10 +139,10 @@ pub fn process_create_subscribe_user( assert_eq!(mgroup.status, MulticastGroupStatus::Activated); // Check if the user is in the allowlist - if value.publisher && !mgroup.pub_allowlist.contains(payer_account.key) { + if value.publisher && !accesspass.mgroup_pub_allowlist.contains(mgroup_account.key) { return Err(DoubleZeroError::NotAllowed.into()); } - if value.subscriber && !mgroup.sub_allowlist.contains(payer_account.key) { + if value.subscriber && !accesspass.mgroup_sub_allowlist.contains(mgroup_account.key) { return Err(DoubleZeroError::NotAllowed.into()); } @@ -183,11 +191,12 @@ pub fn process_create_subscribe_user( validator_pubkey, }; - if value.publisher && !mgroup.publishers.contains(user_account.key) { - mgroup.publishers.push(*user_account.key); + // Update multicastgroup counts + if value.publisher { + mgroup.publisher_count = mgroup.publisher_count.saturating_add(1); } - if value.subscriber && !mgroup.subscribers.contains(user_account.key) { - mgroup.subscribers.push(*user_account.key); + if value.subscriber { + mgroup.subscriber_count = mgroup.subscriber_count.saturating_add(1); } account_write(mgroup_account, &mgroup, payer_account, system_program)?; diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs index 9250a79a1..5eee46534 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/delete.rs @@ -44,6 +44,14 @@ pub fn process_delete_user( // Check the owner of the accounts assert_eq!(user_account.owner, program_id, "Invalid PDA Account Owner"); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } + assert_eq!( + accesspass_account.owner, program_id, + "Invalid AccessPass Account Owner" + ); + assert_eq!( globalstate_account.owner, program_id, "Invalid GlobalState Account Owner" diff --git a/smartcontract/programs/doublezero-serviceability/src/processors/user/resume.rs b/smartcontract/programs/doublezero-serviceability/src/processors/user/resume.rs index e10b2bbcd..a854b14bf 100644 --- a/smartcontract/programs/doublezero-serviceability/src/processors/user/resume.rs +++ b/smartcontract/programs/doublezero-serviceability/src/processors/user/resume.rs @@ -39,6 +39,9 @@ pub fn process_resume_user( // Check the owner of the accounts assert_eq!(user_account.owner, program_id, "Invalid User Account Owner"); + if accesspass_account.data_is_empty() { + return Err(DoubleZeroError::AccessPassNotFound.into()); + } assert_eq!( accesspass_account.owner, program_id, "Invalid AccessPass Account Owner" diff --git a/smartcontract/programs/doublezero-serviceability/src/state/accesspass.rs b/smartcontract/programs/doublezero-serviceability/src/state/accesspass.rs index 336335e2e..7f284f246 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/accesspass.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/accesspass.rs @@ -1,5 +1,6 @@ use crate::{ error::{DoubleZeroError, Validate}, + helper::deserialize_vec_with_capacity, state::accounttype::{AccountType, AccountTypeInfo}, }; use borsh::{BorshDeserialize, BorshSerialize}; @@ -133,6 +134,8 @@ pub struct AccessPass { pub last_access_epoch: u64, // 8 / 0-Rejected / u64::MAX unlimited pub connection_count: u16, // 2 pub status: AccessPassStatus, // 1 + pub mgroup_pub_allowlist: Vec, // Vec<32> - List of multicast groups this AccessPass can publish to + pub mgroup_sub_allowlist: Vec, // Vec<32> - List of multicast groups this AccessPass can subscribe to } impl fmt::Display for AccessPass { @@ -186,6 +189,8 @@ impl TryFrom<&[u8]> for AccessPass { last_access_epoch: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), connection_count: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), status: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), + mgroup_pub_allowlist: deserialize_vec_with_capacity(&mut data).unwrap_or_default(), + mgroup_sub_allowlist: deserialize_vec_with_capacity(&mut data).unwrap_or_default(), }; if out.account_type != AccountType::AccessPass { @@ -260,6 +265,8 @@ mod tests { last_access_epoch: 0, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], }; let data = borsh::to_vec(&val).unwrap(); @@ -292,6 +299,8 @@ mod tests { last_access_epoch: 0, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], }; let data = borsh::to_vec(&val).unwrap(); @@ -325,6 +334,8 @@ mod tests { last_access_epoch: 0, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], }; let err = val.validate(); assert!(err.is_err()); @@ -343,6 +354,8 @@ mod tests { last_access_epoch: 0, connection_count: 0, status: AccessPassStatus::Connected, + mgroup_pub_allowlist: vec![], + mgroup_sub_allowlist: vec![], }; let err = val.validate(); assert!(err.is_err()); diff --git a/smartcontract/programs/doublezero-serviceability/src/state/multicastgroup.rs b/smartcontract/programs/doublezero-serviceability/src/state/multicastgroup.rs index 5f0331676..a13622a1b 100644 --- a/smartcontract/programs/doublezero-serviceability/src/state/multicastgroup.rs +++ b/smartcontract/programs/doublezero-serviceability/src/state/multicastgroup.rs @@ -1,6 +1,5 @@ use crate::{ error::{DoubleZeroError, Validate}, - helper::deserialize_vec_with_capacity, seeds::SEED_MULTICAST_GROUP, state::accounttype::{AccountType, AccountTypeInfo}, }; @@ -72,46 +71,38 @@ pub struct MulticastGroup { pub max_bandwidth: u64, // 8 pub status: MulticastGroupStatus, // 1 pub code: String, // 4 + len - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "doublezero_program_common::serializer::serialize_pubkeylist_as_string", - deserialize_with = "doublezero_program_common::serializer::deserialize_pubkeylist_from_string" - ) - )] - pub pub_allowlist: Vec, // 4 + 32 * len - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "doublezero_program_common::serializer::serialize_pubkeylist_as_string", - deserialize_with = "doublezero_program_common::serializer::deserialize_pubkeylist_from_string" - ) - )] - pub sub_allowlist: Vec, // 4 + 32 * len - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "doublezero_program_common::serializer::serialize_pubkeylist_as_string", - deserialize_with = "doublezero_program_common::serializer::deserialize_pubkeylist_from_string" - ) - )] - pub publishers: Vec, // 4 + 32 * len - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "doublezero_program_common::serializer::serialize_pubkeylist_as_string", - deserialize_with = "doublezero_program_common::serializer::deserialize_pubkeylist_from_string" - ) - )] - pub subscribers: Vec, // 4 + 32 * len + pub publisher_count: u32, // 4 + pub subscriber_count: u32, // 4 } impl fmt::Display for MulticastGroup { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "account_type: {}, owner: {}, index: {}, bump_seed:{}, code: {}, multicast_ip: {}, max_bandwdith: {}, status: {}", - self.account_type, self.owner, self.index, self.bump_seed, self.code, &self.multicast_ip, self.max_bandwidth, self.status, + "MulticastGroup {{ \ + account_type: {}, \ + owner: {}, \ + index: {}, \ + bump_seed: {}, \ + tenant_pk: {}, \ + multicast_ip: {}, \ + max_bandwidth: {}, \ + status: {}, \ + code: \"{}\", \ + publisher_count: {}, \ + subscriber_count: {} \ + }}", + self.account_type, + self.owner, + self.index, + self.bump_seed, + self.tenant_pk, + self.multicast_ip, + self.max_bandwidth, + self.status, + self.code, + self.publisher_count, + self.subscriber_count ) } } @@ -121,23 +112,7 @@ impl AccountTypeInfo for MulticastGroup { SEED_MULTICAST_GROUP } fn size(&self) -> usize { - 1 + 32 - + 16 - + 1 - + 32 - + 4 - + 8 - + 4 - + self.code.len() - + 4 - + self.pub_allowlist.len() * 32 - + 4 - + self.sub_allowlist.len() * 32 - + 4 - + self.publishers.len() * 32 - + 4 - + self.subscribers.len() * 32 - + 1 + 1 + 32 + 16 + 1 + 32 + 4 + 8 + 1 + 4 + self.code.len() + 4 + 4 } fn index(&self) -> u128 { self.index @@ -164,10 +139,8 @@ impl TryFrom<&[u8]> for MulticastGroup { max_bandwidth: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), status: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), code: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), - pub_allowlist: deserialize_vec_with_capacity(&mut data).unwrap_or_default(), - sub_allowlist: deserialize_vec_with_capacity(&mut data).unwrap_or_default(), - publishers: deserialize_vec_with_capacity(&mut data).unwrap_or_default(), - subscribers: deserialize_vec_with_capacity(&mut data).unwrap_or_default(), + publisher_count: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), + subscriber_count: BorshDeserialize::deserialize(&mut data).unwrap_or_default(), }; if out.account_type != AccountType::MulticastGroup { @@ -228,10 +201,8 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test".to_string(), - pub_allowlist: vec![Pubkey::new_unique()], - sub_allowlist: vec![Pubkey::new_unique()], - publishers: vec![Pubkey::new_unique()], - subscribers: vec![Pubkey::new_unique()], + publisher_count: 0, + subscriber_count: 0, }; let err = val.validate(); assert!(err.is_err()); @@ -251,10 +222,8 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test".to_string(), - pub_allowlist: vec![Pubkey::new_unique(), Pubkey::new_unique()], - sub_allowlist: vec![Pubkey::new_unique(), Pubkey::new_unique()], - publishers: vec![Pubkey::new_unique(), Pubkey::new_unique()], - subscribers: vec![Pubkey::new_unique(), Pubkey::new_unique()], + publisher_count: 5, + subscriber_count: 10, }; let data = borsh::to_vec(&val).unwrap(); @@ -273,39 +242,13 @@ mod tests { assert_eq!(val.status, val2.status); assert_eq!(val.account_type, val2.account_type); assert_eq!(val.max_bandwidth, val2.max_bandwidth); + assert_eq!(val.publisher_count, val2.publisher_count); + assert_eq!(val.subscriber_count, val2.subscriber_count); assert_eq!(val.account_type as u8, data[0], "Invalid Account Type"); assert_eq!( val.account_type as u8, val2.account_type as u8, "Invalid Account Type" ); - assert_eq!( - val.pub_allowlist.len(), - val2.pub_allowlist.len(), - "Invalid Pub Allowlist" - ); - assert_eq!( - val.sub_allowlist.len(), - val2.sub_allowlist.len(), - "Invalid Sub Allowlist" - ); - assert_eq!( - val.publishers.len(), - val2.publishers.len(), - "Invalid Publishers" - ); - assert_eq!( - val.subscribers.len(), - val2.subscribers.len(), - "Invalid Subscribers" - ); - assert_eq!( - val.pub_allowlist[0], val2.pub_allowlist[0], - "Invalid Pub Allowlist" - ); - assert_eq!( - val.sub_allowlist[0], val2.sub_allowlist[0], - "Invalid Sub Allowlist" - ); assert_eq!(data.len(), val.size(), "Invalid Size"); } } diff --git a/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_publisher_test.rs b/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_publisher_test.rs index 11c43ca15..919e354c5 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_publisher_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_publisher_test.rs @@ -2,14 +2,20 @@ use doublezero_serviceability::{ entrypoint::*, instructions::*, pda::*, - processors::multicastgroup::{ - activate::MulticastGroupActivateArgs, - allowlist::publisher::{ - add::AddMulticastGroupPubAllowlistArgs, remove::RemoveMulticastGroupPubAllowlistArgs, + processors::{ + accesspass::set::SetAccessPassArgs, + multicastgroup::{ + activate::MulticastGroupActivateArgs, + allowlist::publisher::{ + add::AddMulticastGroupPubAllowlistArgs, + remove::RemoveMulticastGroupPubAllowlistArgs, + }, + create::MulticastGroupCreateArgs, }, - create::MulticastGroupCreateArgs, }, - state::{accounttype::AccountType, multicastgroup::MulticastGroupStatus}, + state::{ + accesspass::AccessPassType, accounttype::AccountType, multicastgroup::MulticastGroupStatus, + }, }; use solana_program_test::*; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signer::Signer}; @@ -31,6 +37,9 @@ async fn test_multicast_publisher_allowlist() { /***********************************************************************************************************************************/ println!("🟢 1. Global Initialization..."); + let user_payer = payer.pubkey(); + let client_ip = [100, 0, 0, 1].into(); + let (program_config_pubkey, _) = get_program_config_pda(&program_id); let (globalstate_pubkey, _) = get_globalstate_pda(&program_id); @@ -123,38 +132,61 @@ async fn test_multicast_publisher_allowlist() { println!("✅"); /*****************************************************************************************************************************************************/ - println!("🟢 4. Add Allowlist ..."); + println!("🟢 4. Set AccessPass..."); - let (multicastgroup_pubkey, _) = get_multicastgroup_pda(&program_id, 1); + let (accesspass_pubkey, _) = get_accesspass_pda(&program_id, &client_ip, &user_payer); - let allowlist_pubkey = Pubkey::new_unique(); + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs { + accesspass_type: AccessPassType::Prepaid, + client_ip, + last_access_epoch: 100, + }), + vec![ + AccountMeta::new(accesspass_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + AccountMeta::new(user_payer, false), + ], + &payer, + ) + .await; + + /*****************************************************************************************************************************************************/ + println!("🟢 5. Add Allowlist ..."); execute_transaction( &mut banks_client, recent_blockhash, program_id, DoubleZeroInstruction::AddMulticastGroupPubAllowlist(AddMulticastGroupPubAllowlistArgs { - pubkey: allowlist_pubkey, + client_ip, + user_payer, }), - vec![AccountMeta::new(multicastgroup_pubkey, false)], + vec![ + AccountMeta::new(multicastgroup_pubkey, false), + AccountMeta::new(accesspass_pubkey, false), + ], &payer, ) .await; - let mgroup = get_account_data(&mut banks_client, multicastgroup_pubkey) + let accesspass = get_account_data(&mut banks_client, accesspass_pubkey) .await .expect("Unable to get Account") - .get_multicastgroup() + .get_accesspass() .unwrap(); - assert_eq!(mgroup.account_type, AccountType::MulticastGroup); - assert_eq!(mgroup.pub_allowlist.len(), 1); - assert!(mgroup.pub_allowlist.contains(&allowlist_pubkey)); - assert_eq!(mgroup.status, MulticastGroupStatus::Activated); + assert_eq!(accesspass.account_type, AccountType::AccessPass); + assert!(accesspass + .mgroup_pub_allowlist + .contains(&multicastgroup_pubkey)); println!("✅"); /*****************************************************************************************************************************************************/ - println!("🟢 5. Remove Allowlist ..."); + println!("🟢 6. Remove Allowlist ..."); let (multicastgroup_pubkey, _) = get_multicastgroup_pda(&program_id, 1); @@ -164,23 +196,26 @@ async fn test_multicast_publisher_allowlist() { program_id, DoubleZeroInstruction::RemoveMulticastGroupPubAllowlist( RemoveMulticastGroupPubAllowlistArgs { - pubkey: allowlist_pubkey, + client_ip, + user_payer, }, ), - vec![AccountMeta::new(multicastgroup_pubkey, false)], + vec![ + AccountMeta::new(multicastgroup_pubkey, false), + AccountMeta::new(accesspass_pubkey, false), + ], &payer, ) .await; - let mgroup = get_account_data(&mut banks_client, multicastgroup_pubkey) + let accesspass = get_account_data(&mut banks_client, accesspass_pubkey) .await .expect("Unable to get Account") - .get_multicastgroup() + .get_accesspass() .unwrap(); - assert_eq!(mgroup.account_type, AccountType::MulticastGroup); - assert_eq!(mgroup.pub_allowlist.len(), 0); - assert_eq!(mgroup.status, MulticastGroupStatus::Activated); + assert_eq!(accesspass.account_type, AccountType::AccessPass); + assert_eq!(accesspass.mgroup_pub_allowlist.len(), 0); println!("✅"); /*****************************************************************************************************************************************************/ diff --git a/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_subcriber_test.rs b/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_subcriber_test.rs index 8d5e70757..4a51045f3 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_subcriber_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_allowlist_subcriber_test.rs @@ -2,14 +2,20 @@ use doublezero_serviceability::{ entrypoint::*, instructions::*, pda::*, - processors::multicastgroup::{ - activate::MulticastGroupActivateArgs, - allowlist::subscriber::{ - add::AddMulticastGroupSubAllowlistArgs, remove::RemoveMulticastGroupSubAllowlistArgs, + processors::{ + accesspass::set::SetAccessPassArgs, + multicastgroup::{ + activate::MulticastGroupActivateArgs, + allowlist::subscriber::{ + add::AddMulticastGroupSubAllowlistArgs, + remove::RemoveMulticastGroupSubAllowlistArgs, + }, + create::MulticastGroupCreateArgs, }, - create::MulticastGroupCreateArgs, }, - state::{accounttype::AccountType, multicastgroup::MulticastGroupStatus}, + state::{ + accesspass::AccessPassType, accounttype::AccountType, multicastgroup::MulticastGroupStatus, + }, }; use solana_program_test::*; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signer::Signer}; @@ -31,6 +37,9 @@ async fn test_multicast_subscriber_allowlist() { /***********************************************************************************************************************************/ println!("🟢 1. Global Initialization..."); + let user_payer = payer.pubkey(); + let client_ip = [100, 0, 0, 1].into(); + let (program_config_pubkey, _) = get_program_config_pda(&program_id); let (globalstate_pubkey, _) = get_globalstate_pda(&program_id); @@ -123,40 +132,63 @@ async fn test_multicast_subscriber_allowlist() { println!("✅"); /*****************************************************************************************************************************************************/ - println!("🟢 4. Add Allowlist ..."); + println!("🟢 4. Create AccessPass..."); - let (multicastgroup_pubkey, _) = get_multicastgroup_pda(&program_id, 1); + let (accesspass_pubkey, _) = get_accesspass_pda(&program_id, &client_ip, &user_payer); + + execute_transaction( + &mut banks_client, + recent_blockhash, + program_id, + DoubleZeroInstruction::SetAccessPass(SetAccessPassArgs { + accesspass_type: AccessPassType::Prepaid, + client_ip, + last_access_epoch: 100, + }), + vec![ + AccountMeta::new(accesspass_pubkey, false), + AccountMeta::new(globalstate_pubkey, false), + AccountMeta::new(user_payer, false), + ], + &payer, + ) + .await; + + /*****************************************************************************************************************************************************/ + println!("🟢 5. Add Allowlist ..."); - let allowlist_pubkey = Pubkey::new_unique(); + let (accesspass_pubkey, _) = get_accesspass_pda(&program_id, &client_ip, &user_payer); execute_transaction( &mut banks_client, recent_blockhash, program_id, DoubleZeroInstruction::AddMulticastGroupSubAllowlist(AddMulticastGroupSubAllowlistArgs { - pubkey: allowlist_pubkey, + client_ip, + user_payer, }), - vec![AccountMeta::new(multicastgroup_pubkey, false)], + vec![ + AccountMeta::new(multicastgroup_pubkey, false), + AccountMeta::new(accesspass_pubkey, false), + ], &payer, ) .await; - let mgroup = get_account_data(&mut banks_client, multicastgroup_pubkey) + let accesspass = get_account_data(&mut banks_client, accesspass_pubkey) .await .expect("Unable to get Account") - .get_multicastgroup() + .get_accesspass() .unwrap(); - assert_eq!(mgroup.account_type, AccountType::MulticastGroup); - assert_eq!(mgroup.sub_allowlist.len(), 1); - assert!(mgroup.sub_allowlist.contains(&allowlist_pubkey)); - assert_eq!(mgroup.status, MulticastGroupStatus::Activated); + assert_eq!(accesspass.account_type, AccountType::AccessPass); + assert!(accesspass + .mgroup_sub_allowlist + .contains(&multicastgroup_pubkey)); println!("✅"); /*****************************************************************************************************************************************************/ - println!("🟢 5. Remove Allowlist ..."); - - let (multicastgroup_pubkey, _) = get_multicastgroup_pda(&program_id, 1); + println!("🟢 6. Remove Allowlist ..."); execute_transaction( &mut banks_client, @@ -164,23 +196,26 @@ async fn test_multicast_subscriber_allowlist() { program_id, DoubleZeroInstruction::RemoveMulticastGroupSubAllowlist( RemoveMulticastGroupSubAllowlistArgs { - pubkey: allowlist_pubkey, + client_ip, + user_payer, }, ), - vec![AccountMeta::new(multicastgroup_pubkey, false)], + vec![ + AccountMeta::new(multicastgroup_pubkey, false), + AccountMeta::new(accesspass_pubkey, false), + ], &payer, ) .await; - let mgroup = get_account_data(&mut banks_client, multicastgroup_pubkey) + let accesspass = get_account_data(&mut banks_client, accesspass_pubkey) .await .expect("Unable to get Account") - .get_multicastgroup() + .get_accesspass() .unwrap(); - assert_eq!(mgroup.account_type, AccountType::MulticastGroup); - assert_eq!(mgroup.sub_allowlist.len(), 0); - assert_eq!(mgroup.status, MulticastGroupStatus::Activated); + assert_eq!(accesspass.account_type, AccountType::AccessPass); + assert_eq!(accesspass.mgroup_sub_allowlist.len(), 0); println!("✅"); /*****************************************************************************************************************************************************/ diff --git a/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_test.rs b/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_test.rs index 42e557523..5129e9281 100644 --- a/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_test.rs +++ b/smartcontract/programs/doublezero-serviceability/tests/multicastgroup_test.rs @@ -1,3 +1,5 @@ +use std::net::Ipv4Addr; + use doublezero_serviceability::{ entrypoint::*, instructions::*, @@ -179,6 +181,8 @@ async fn test_multicastgroup() { code: Some("la2".to_string()), multicast_ip: Some([239, 1, 1, 2].into()), max_bandwidth: Some(2000), + publisher_count: Some(5), + subscriber_count: Some(10), }), vec![ AccountMeta::new(multicastgroup_pubkey, false), @@ -195,6 +199,10 @@ async fn test_multicastgroup() { .unwrap(); assert_eq!(multicastgroup_la.account_type, AccountType::MulticastGroup); assert_eq!(multicastgroup_la.code, "la2".to_string()); + assert_eq!(multicastgroup_la.multicast_ip, Ipv4Addr::new(239, 1, 1, 2)); + assert_eq!(multicastgroup_la.max_bandwidth, 2000); + assert_eq!(multicastgroup_la.publisher_count, 5); + assert_eq!(multicastgroup_la.subscriber_count, 10); assert_eq!(multicastgroup_la.status, MulticastGroupStatus::Activated); println!("✅ MulticastGroup updated"); diff --git a/smartcontract/sdk/go/serviceability/client_test.go b/smartcontract/sdk/go/serviceability/client_test.go index c3649c0e7..217f07c73 100644 --- a/smartcontract/sdk/go/serviceability/client_test.go +++ b/smartcontract/sdk/go/serviceability/client_test.go @@ -383,10 +383,6 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { MaxBandwidth: 1000000000, Status: MulticastGroupStatusActivated, Code: "jito", - PubAllowList: [][32]uint8{{0xba, 0xae, 0x1c, 0xe3, 0xbc, 0xe5, 0x13, 0x0a, 0xe5, 0xf4, 0x6b, 0x6d, 0x47, 0x88, 0x4a, 0xb6, 0x0b, 0x6d, 0x22, 0xf5, 0x5b, 0x0c, 0x0c, 0xfa, 0xcf, 0x14, 0xab, 0xe7, 0xea, 0x31, 0x18, 0xae}}, - SubAllowList: [][32]uint8{{0xba, 0xae, 0x1c, 0xe3, 0xbc, 0xe5, 0x13, 0x0a, 0xe5, 0xf4, 0x6b, 0x6d, 0x47, 0x88, 0x4a, 0xb6, 0x0b, 0x6d, 0x22, 0xf5, 0x5b, 0x0c, 0x0c, 0xfa, 0xcf, 0x14, 0xab, 0xe7, 0xea, 0x31, 0x18, 0xae}}, - Publishers: [][32]uint8{{0x59, 0xd1, 0x27, 0xe5, 0xab, 0xbd, 0x5c, 0xe8, 0x8c, 0x1d, 0xe4, 0xab, 0xe7, 0x0b, 0x13, 0x2b, 0x4c, 0x79, 0xd4, 0xa1, 0xff, 0xe7, 0x81, 0x95, 0x2a, 0x8b, 0xdf, 0x13, 0x80, 0x1d, 0x2c, 0xb6}, {0x3a, 0x31, 0x6a, 0x45, 0x05, 0xa3, 0x9d, 0x60, 0x26, 0xa5, 0x5b, 0xf2, 0x89, 0x4e, 0x30, 0xba, 0xd3, 0x3b, 0xc1, 0x63, 0x1c, 0xe1, 0xbd, 0x92, 0x5f, 0x02, 0xab, 0x4c, 0x79, 0x94, 0xe9, 0xd4}}, - Subscribers: [][32]uint8{{0x41, 0xc6, 0x96, 0x40, 0x53, 0xcf, 0x55, 0xd2, 0x92, 0x54, 0x72, 0xdb, 0xe0, 0x1a, 0xfb, 0xc3, 0x27, 0xf5, 0xab, 0xfd, 0xb9, 0x17, 0xec, 0x23, 0x4e, 0xca, 0xbc, 0x09, 0xe5, 0x29, 0x0b, 0x2b}, {0x3a, 0x31, 0x6a, 0x45, 0x05, 0xa3, 0x9d, 0x60, 0x26, 0xa5, 0x5b, 0xf2, 0x89, 0x4e, 0x30, 0xba, 0xd3, 0x3b, 0xc1, 0x63, 0x1c, 0xe1, 0xbd, 0x92, 0x5f, 0x02, 0xab, 0x4c, 0x79, 0x94, 0xe9, 0xd4}}, PubKey: pubkeys[6], }, }, diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index 41885162a..f87661e94 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -155,10 +155,6 @@ func DeserializeMulticastGroup(reader *ByteReader, multicastgroup *MulticastGrou multicastgroup.MaxBandwidth = reader.ReadU64() multicastgroup.Status = MulticastGroupStatus(reader.ReadU8()) multicastgroup.Code = reader.ReadString() - multicastgroup.PubAllowList = reader.ReadPubkeySlice() - multicastgroup.SubAllowList = reader.ReadPubkeySlice() - multicastgroup.Publishers = reader.ReadPubkeySlice() - multicastgroup.Subscribers = reader.ReadPubkeySlice() multicastgroup.PubKey = reader.ReadPubkey() } diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index f7ab32864..4eb481773 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -565,20 +565,18 @@ const ( ) type MulticastGroup struct { - AccountType AccountType - Owner [32]uint8 - Index Uint128 - Bump_seed uint8 - TenantPubKey [32]uint8 - MulticastIp [4]uint8 - MaxBandwidth uint64 - Status MulticastGroupStatus - Code string - PubAllowList [][32]uint8 - SubAllowList [][32]uint8 - Publishers [][32]uint8 - Subscribers [][32]uint8 - PubKey [32]byte + AccountType AccountType + Owner [32]uint8 + Index Uint128 + Bump_seed uint8 + TenantPubKey [32]uint8 + MulticastIp [4]uint8 + MaxBandwidth uint64 + Status MulticastGroupStatus + Code string + PublisherCount uint32 + SubscriberCount uint32 + PubKey [32]byte } type ProgramVersion struct { diff --git a/smartcontract/sdk/rs/src/commands/allowlist/mod.rs b/smartcontract/sdk/rs/src/commands/allowlist/mod.rs index 6ac2c0cd1..68a8688a2 100644 --- a/smartcontract/sdk/rs/src/commands/allowlist/mod.rs +++ b/smartcontract/sdk/rs/src/commands/allowlist/mod.rs @@ -1,3 +1,2 @@ pub mod device; pub mod foundation; -pub mod user; diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/add.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/add.rs index 380158274..1a91102e3 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/add.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/add.rs @@ -1,34 +1,39 @@ use crate::{commands::multicastgroup::get::GetMulticastGroupCommand, DoubleZeroClient}; use doublezero_serviceability::{ - instructions::DoubleZeroInstruction, + instructions::DoubleZeroInstruction, pda::get_accesspass_pda, processors::multicastgroup::allowlist::publisher::add::AddMulticastGroupPubAllowlistArgs, }; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Signature}; +use std::net::Ipv4Addr; #[derive(Debug, PartialEq, Clone)] pub struct AddMulticastGroupPubAllowlistCommand { pub pubkey_or_code: String, - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl AddMulticastGroupPubAllowlistCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { - let (pda_pubkey, mgroup) = GetMulticastGroupCommand { + let (mgroup_pubkey, _mgroup) = GetMulticastGroupCommand { pubkey_or_code: self.pubkey_or_code.clone(), } .execute(client)?; - if mgroup.pub_allowlist.contains(&self.pubkey) { - eyre::bail!("Publisher is already in the allowlist"); - } + let (accesspass_pk, _) = + get_accesspass_pda(&client.get_program_id(), &self.client_ip, &self.user_payer); client.execute_transaction( DoubleZeroInstruction::AddMulticastGroupPubAllowlist( AddMulticastGroupPubAllowlistArgs { - pubkey: self.pubkey, + client_ip: self.client_ip, + user_payer: self.user_payer, }, ), - vec![AccountMeta::new(pda_pubkey, false)], + vec![ + AccountMeta::new(mgroup_pubkey, false), + AccountMeta::new(accesspass_pk, false), + ], ) } } @@ -66,10 +71,8 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test_code".to_string(), - publishers: vec![], - subscribers: vec![], - pub_allowlist: vec![], - sub_allowlist: vec![], + publisher_count: 5, + subscriber_count: 10, }; let cloned_mgroup = mgroup.clone(); @@ -90,7 +93,10 @@ mod tests { .expect_execute_transaction() .with( predicate::eq(DoubleZeroInstruction::AddMulticastGroupPubAllowlist( - AddMulticastGroupPubAllowlistArgs { pubkey }, + AddMulticastGroupPubAllowlistArgs { + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, + }, )), predicate::always(), ) @@ -98,7 +104,8 @@ mod tests { let res = AddMulticastGroupPubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/list.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/list.rs deleted file mode 100644 index 324fca3d5..000000000 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/list.rs +++ /dev/null @@ -1,121 +0,0 @@ -use doublezero_serviceability::state::{accountdata::AccountData, accounttype::AccountType}; -use solana_sdk::pubkey::Pubkey; - -use crate::{utils::parse_pubkey, DoubleZeroClient}; - -#[derive(Debug, PartialEq, Clone)] -pub struct ListMulticastGroupPubAllowlistCommand { - pub pubkey_or_code: String, -} - -impl ListMulticastGroupPubAllowlistCommand { - pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result> { - match parse_pubkey(&self.pubkey_or_code) { - Some(pk) => match client.get(pk)? { - AccountData::MulticastGroup(mgroup) => Ok(mgroup.pub_allowlist), - _ => Err(eyre::eyre!("Invalid Account Type")), - }, - None => client - .gets(AccountType::MulticastGroup)? - .into_iter() - .find(|(_, v)| match v { - AccountData::MulticastGroup(mgroup) => mgroup.code == self.pubkey_or_code, - _ => false, - }) - .map(|(_pk, v)| match v { - AccountData::MulticastGroup(mgroup) => Ok(mgroup.pub_allowlist), - _ => Err(eyre::eyre!("Invalid Account Type")), - }) - .unwrap_or_else(|| { - Err(eyre::eyre!( - "MulticastGroup with code {} not found", - self.pubkey_or_code - )) - }), - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - commands::multicastgroup::allowlist::publisher::list::ListMulticastGroupPubAllowlistCommand, - tests::utils::create_test_client, - }; - use doublezero_serviceability::{ - instructions::DoubleZeroInstruction, - processors::multicastgroup::allowlist::publisher::add::AddMulticastGroupPubAllowlistArgs, - state::{ - accountdata::AccountData, - accounttype::AccountType, - multicastgroup::{MulticastGroup, MulticastGroupStatus}, - }, - }; - use mockall::predicate; - use solana_sdk::{pubkey::Pubkey, signature::Signature}; - - #[test] - fn test_commands_multicastgroup_allowlist_publisher_list() { - let mut client = create_test_client(); - - let pubkey = Pubkey::new_unique(); - let mgroup = MulticastGroup { - account_type: AccountType::MulticastGroup, - index: 1, - bump_seed: 1, - owner: Pubkey::new_unique(), - tenant_pk: Pubkey::new_unique(), - multicast_ip: [239, 1, 1, 1].into(), - max_bandwidth: 1000, - status: MulticastGroupStatus::Activated, - code: "test_code".to_string(), - publishers: vec![], - subscribers: vec![], - pub_allowlist: vec![], - sub_allowlist: vec![], - }; - - let cloned_mgroup = mgroup.clone(); - client - .expect_get() - .with(predicate::eq(pubkey)) - .returning(move |_| Ok(AccountData::MulticastGroup(cloned_mgroup.clone()))); - let cloned_mgroup = mgroup.clone(); - client - .expect_gets() - .with(predicate::eq(AccountType::MulticastGroup)) - .returning(move |_| { - let mut map = std::collections::HashMap::new(); - map.insert(pubkey, AccountData::MulticastGroup(cloned_mgroup.clone())); - Ok(map) - }); - client - .expect_execute_transaction() - .with( - predicate::eq(DoubleZeroInstruction::AddMulticastGroupPubAllowlist( - AddMulticastGroupPubAllowlistArgs { pubkey }, - )), - predicate::always(), - ) - .returning(|_, _| Ok(Signature::new_unique())); - - // list by code - let res = ListMulticastGroupPubAllowlistCommand { - pubkey_or_code: "test_code".to_string(), - } - .execute(&client); - assert!(res.is_ok()); - let allowlist = res.unwrap(); - assert!( - allowlist.is_empty(), - "Expected empty allowlist, got: {allowlist:?}", - ); - - // list with invalid code - let res = ListMulticastGroupPubAllowlistCommand { - pubkey_or_code: "test&code".to_string(), - } - .execute(&client); - assert!(res.is_err()); - } -} diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/mod.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/mod.rs index 393a75332..6d1eb9784 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/mod.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/mod.rs @@ -1,3 +1,2 @@ pub mod add; -pub mod list; pub mod remove; diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/remove.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/remove.rs index 57c742379..a6094b27e 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/remove.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/publisher/remove.rs @@ -1,5 +1,7 @@ +use std::net::Ipv4Addr; + use doublezero_serviceability::{ - instructions::DoubleZeroInstruction, + instructions::DoubleZeroInstruction, pda::get_accesspass_pda, processors::multicastgroup::allowlist::publisher::remove::RemoveMulticastGroupPubAllowlistArgs, }; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Signature}; @@ -9,27 +11,31 @@ use crate::{commands::multicastgroup::get::GetMulticastGroupCommand, DoubleZeroC #[derive(Debug, PartialEq, Clone)] pub struct RemoveMulticastGroupPubAllowlistCommand { pub pubkey_or_code: String, - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl RemoveMulticastGroupPubAllowlistCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { - let (pda_pubkey, mgroup) = GetMulticastGroupCommand { + let (mgroup_pubkey, _mgroup) = GetMulticastGroupCommand { pubkey_or_code: self.pubkey_or_code.clone(), } .execute(client)?; - if !mgroup.pub_allowlist.contains(&self.pubkey) { - eyre::bail!("Publisher is not in the allowlist"); - } + let (accesspass_pk, _) = + get_accesspass_pda(&client.get_program_id(), &self.client_ip, &self.user_payer); client.execute_transaction( DoubleZeroInstruction::RemoveMulticastGroupPubAllowlist( RemoveMulticastGroupPubAllowlistArgs { - pubkey: self.pubkey, + client_ip: self.client_ip, + user_payer: self.user_payer, }, ), - vec![AccountMeta::new(pda_pubkey, false)], + vec![ + AccountMeta::new(mgroup_pubkey, false), + AccountMeta::new(accesspass_pk, false), + ], ) } } @@ -67,10 +73,8 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test_code".to_string(), - publishers: vec![], - subscribers: vec![], - pub_allowlist: vec![pubkey], - sub_allowlist: vec![], + publisher_count: 5, + subscriber_count: 10, }; let cloned_mgroup = mgroup.clone(); @@ -91,7 +95,10 @@ mod tests { .expect_execute_transaction() .with( predicate::eq(DoubleZeroInstruction::RemoveMulticastGroupPubAllowlist( - RemoveMulticastGroupPubAllowlistArgs { pubkey }, + RemoveMulticastGroupPubAllowlistArgs { + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, + }, )), predicate::always(), ) @@ -100,7 +107,8 @@ mod tests { // remove publisher with valid code let res = RemoveMulticastGroupPubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); assert!(res.is_ok()); @@ -108,7 +116,8 @@ mod tests { // error attempting to remove publisher with code containing invalid char let res = RemoveMulticastGroupPubAllowlistCommand { pubkey_or_code: "test^code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); assert!(res.is_err()); diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/add.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/add.rs index 64c37ff6b..9296b4034 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/add.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/add.rs @@ -1,5 +1,7 @@ +use std::net::Ipv4Addr; + use doublezero_serviceability::{ - instructions::DoubleZeroInstruction, + instructions::DoubleZeroInstruction, pda::get_accesspass_pda, processors::multicastgroup::allowlist::subscriber::add::AddMulticastGroupSubAllowlistArgs, }; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Signature}; @@ -9,27 +11,31 @@ use crate::{commands::multicastgroup::get::GetMulticastGroupCommand, DoubleZeroC #[derive(Debug, PartialEq, Clone)] pub struct AddMulticastGroupSubAllowlistCommand { pub pubkey_or_code: String, - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl AddMulticastGroupSubAllowlistCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { - let (pda_pubkey, mgroup) = GetMulticastGroupCommand { + let (pda_pubkey, _mgroup) = GetMulticastGroupCommand { pubkey_or_code: self.pubkey_or_code.clone(), } .execute(client)?; - if mgroup.sub_allowlist.contains(&self.pubkey) { - eyre::bail!("Publisher is already in the allowlist"); - } + let (accesspass_pk, _) = + get_accesspass_pda(&client.get_program_id(), &self.client_ip, &self.user_payer); client.execute_transaction( DoubleZeroInstruction::AddMulticastGroupSubAllowlist( AddMulticastGroupSubAllowlistArgs { - pubkey: self.pubkey, + client_ip: self.client_ip, + user_payer: self.user_payer, }, ), - vec![AccountMeta::new(pda_pubkey, false)], + vec![ + AccountMeta::new(pda_pubkey, false), + AccountMeta::new(accesspass_pk, false), + ], ) } } @@ -67,10 +73,8 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test_code".to_string(), - publishers: vec![], - subscribers: vec![], - pub_allowlist: vec![], - sub_allowlist: vec![], + publisher_count: 5, + subscriber_count: 10, }; let cloned_mgroup = mgroup.clone(); @@ -91,7 +95,10 @@ mod tests { .expect_execute_transaction() .with( predicate::eq(DoubleZeroInstruction::AddMulticastGroupSubAllowlist( - AddMulticastGroupSubAllowlistArgs { pubkey }, + AddMulticastGroupSubAllowlistArgs { + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, + }, )), predicate::always(), ) @@ -100,7 +107,8 @@ mod tests { // add using valid code let res = AddMulticastGroupSubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); assert!(res.is_ok()); @@ -108,7 +116,8 @@ mod tests { // error attempting to add code with invalid char(s) let res = AddMulticastGroupSubAllowlistCommand { pubkey_or_code: "test code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); assert!(res.is_err()); diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/list.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/list.rs deleted file mode 100644 index a14c3031c..000000000 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/list.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::{utils::parse_pubkey, DoubleZeroClient}; -use doublezero_serviceability::state::{accountdata::AccountData, accounttype::AccountType}; -use solana_sdk::pubkey::Pubkey; - -#[derive(Debug, PartialEq, Clone)] -pub struct ListMulticastGroupSubAllowlistCommand { - pub pubkey_or_code: String, -} - -impl ListMulticastGroupSubAllowlistCommand { - pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result> { - match parse_pubkey(&self.pubkey_or_code) { - Some(pk) => match client.get(pk)? { - AccountData::MulticastGroup(mgroup) => Ok(mgroup.sub_allowlist), - _ => Err(eyre::eyre!("Invalid Account Type")), - }, - None => client - .gets(AccountType::MulticastGroup)? - .into_iter() - .find(|(_, v)| match v { - AccountData::MulticastGroup(mgroup) => mgroup.code == self.pubkey_or_code, - _ => false, - }) - .map(|(_pk, v)| match v { - AccountData::MulticastGroup(mgroup) => Ok(mgroup.sub_allowlist), - _ => Err(eyre::eyre!("Invalid Account Type")), - }) - .unwrap_or_else(|| { - Err(eyre::eyre!( - "MulticastGroup with code {} not found", - self.pubkey_or_code - )) - }), - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - commands::multicastgroup::allowlist::subscriber::list::ListMulticastGroupSubAllowlistCommand, - tests::utils::create_test_client, - }; - use doublezero_serviceability::{ - instructions::DoubleZeroInstruction, - processors::multicastgroup::allowlist::publisher::add::AddMulticastGroupPubAllowlistArgs, - state::{ - accountdata::AccountData, - accounttype::AccountType, - multicastgroup::{MulticastGroup, MulticastGroupStatus}, - }, - }; - use mockall::predicate; - use solana_sdk::{pubkey::Pubkey, signature::Signature}; - - #[test] - fn test_commands_multicastgroup_allowlist_subscriber_list() { - let mut client = create_test_client(); - - let pubkey = Pubkey::new_unique(); - let mgroup = MulticastGroup { - account_type: AccountType::MulticastGroup, - index: 1, - bump_seed: 1, - owner: Pubkey::new_unique(), - tenant_pk: Pubkey::new_unique(), - multicast_ip: [239, 1, 1, 1].into(), - max_bandwidth: 1000, - status: MulticastGroupStatus::Activated, - code: "test_code".to_string(), - publishers: vec![], - subscribers: vec![], - pub_allowlist: vec![], - sub_allowlist: vec![], - }; - - let cloned_mgroup = mgroup.clone(); - client - .expect_get() - .with(predicate::eq(pubkey)) - .returning(move |_| Ok(AccountData::MulticastGroup(cloned_mgroup.clone()))); - let cloned_mgroup = mgroup.clone(); - client - .expect_gets() - .with(predicate::eq(AccountType::MulticastGroup)) - .returning(move |_| { - let mut map = std::collections::HashMap::new(); - map.insert(pubkey, AccountData::MulticastGroup(cloned_mgroup.clone())); - Ok(map) - }); - client - .expect_execute_transaction() - .with( - predicate::eq(DoubleZeroInstruction::AddMulticastGroupPubAllowlist( - AddMulticastGroupPubAllowlistArgs { pubkey }, - )), - predicate::always(), - ) - .returning(|_, _| Ok(Signature::new_unique())); - - // list with valid code - let res = ListMulticastGroupSubAllowlistCommand { - pubkey_or_code: "test_code".to_string(), - } - .execute(&client); - - assert!(res.is_ok()); - let allowlist = res.unwrap(); - assert!( - allowlist.is_empty(), - "Expected empty allowlist, got: {allowlist:?}" - ); - - // list with code containing invalid character - let res = ListMulticastGroupSubAllowlistCommand { - pubkey_or_code: "test%code".to_string(), - } - .execute(&client); - assert!(res.is_err()); - } -} diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/mod.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/mod.rs index 393a75332..6d1eb9784 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/mod.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/mod.rs @@ -1,3 +1,2 @@ pub mod add; -pub mod list; pub mod remove; diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/remove.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/remove.rs index e45459788..fdf481bad 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/remove.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/allowlist/subscriber/remove.rs @@ -1,5 +1,7 @@ +use std::net::Ipv4Addr; + use doublezero_serviceability::{ - instructions::DoubleZeroInstruction, + instructions::DoubleZeroInstruction, pda::get_accesspass_pda, processors::multicastgroup::allowlist::subscriber::remove::RemoveMulticastGroupSubAllowlistArgs, }; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Signature}; @@ -9,27 +11,31 @@ use crate::{commands::multicastgroup::get::GetMulticastGroupCommand, DoubleZeroC #[derive(Debug, PartialEq, Clone)] pub struct RemoveMulticastGroupSubAllowlistCommand { pub pubkey_or_code: String, - pub pubkey: Pubkey, + pub client_ip: Ipv4Addr, + pub user_payer: Pubkey, } impl RemoveMulticastGroupSubAllowlistCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { - let (pda_pubkey, mgroup) = GetMulticastGroupCommand { + let (pda_pubkey, _mgroup) = GetMulticastGroupCommand { pubkey_or_code: self.pubkey_or_code.clone(), } .execute(client)?; - if !mgroup.sub_allowlist.contains(&self.pubkey) { - eyre::bail!("Publisher is not in the allowlist"); - } + let (accesspass_pk, _) = + get_accesspass_pda(&client.get_program_id(), &self.client_ip, &self.user_payer); client.execute_transaction( DoubleZeroInstruction::RemoveMulticastGroupSubAllowlist( RemoveMulticastGroupSubAllowlistArgs { - pubkey: self.pubkey, + client_ip: self.client_ip, + user_payer: self.user_payer, }, ), - vec![AccountMeta::new(pda_pubkey, false)], + vec![ + AccountMeta::new(pda_pubkey, false), + AccountMeta::new(accesspass_pk, false), + ], ) } } @@ -67,10 +73,8 @@ mod tests { max_bandwidth: 1000, status: MulticastGroupStatus::Activated, code: "test_code".to_string(), - publishers: vec![], - subscribers: vec![], - pub_allowlist: vec![], - sub_allowlist: vec![pubkey], + publisher_count: 5, + subscriber_count: 10, }; let cloned_mgroup = mgroup.clone(); @@ -91,7 +95,10 @@ mod tests { .expect_execute_transaction() .with( predicate::eq(DoubleZeroInstruction::RemoveMulticastGroupSubAllowlist( - RemoveMulticastGroupSubAllowlistArgs { pubkey }, + RemoveMulticastGroupSubAllowlistArgs { + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, + }, )), predicate::always(), ) @@ -100,7 +107,8 @@ mod tests { // remove with valid code let res = RemoveMulticastGroupSubAllowlistCommand { pubkey_or_code: "test_code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); assert!(res.is_ok()); @@ -108,7 +116,8 @@ mod tests { // error removing with invalid code character let res = RemoveMulticastGroupSubAllowlistCommand { pubkey_or_code: "test%code".to_string(), - pubkey, + client_ip: [192, 168, 1, 1].into(), + user_payer: pubkey, } .execute(&client); assert!(res.is_err()); diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/delete.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/delete.rs index a9f227df7..aaa7708d6 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/delete.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/delete.rs @@ -1,10 +1,4 @@ -use crate::{ - commands::{ - globalstate::get::GetGlobalStateCommand, - multicastgroup::subscribe::SubscribeMulticastGroupCommand, - }, - DoubleZeroClient, -}; +use crate::{commands::globalstate::get::GetGlobalStateCommand, DoubleZeroClient}; use doublezero_serviceability::{ instructions::DoubleZeroInstruction, processors::multicastgroup::delete::MulticastGroupDeleteArgs, @@ -23,21 +17,8 @@ impl DeleteMulticastGroupCommand { .map_err(|_err| eyre::eyre!("Globalstate not initialized"))?; let mgroup_pubkey = self.pubkey; - let mgroup = client - .get(mgroup_pubkey) - .map_err(|_| eyre::eyre!("MulticastGroup not found ({})", mgroup_pubkey))? - .get_multicastgroup() - .map_err(|e| eyre::eyre!(e))?; - for user_pk in mgroup.publishers.iter().chain(mgroup.subscribers.iter()) { - SubscribeMulticastGroupCommand { - group_pk: mgroup_pubkey, - user_pk: *user_pk, - publisher: false, - subscriber: false, - } - .execute(client)?; - } + // TODO: Check for existing AccessPass referencing this multicast group pubkey in either publishers or subscribers lists before deletion client.execute_transaction( DoubleZeroInstruction::DeleteMulticastGroup(MulticastGroupDeleteArgs {}), @@ -85,11 +66,9 @@ mod tests { multicast_ip: Ipv4Addr::UNSPECIFIED, max_bandwidth: 0, status: MulticastGroupStatus::Activated, - pub_allowlist: vec![client.get_payer()], - sub_allowlist: vec![client.get_payer()], - publishers: vec![], - subscribers: vec![], owner: Pubkey::default(), + publisher_count: 1, + subscriber_count: 0, }; let mgroup_cloned = mgroup.clone(); diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/subscribe.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/subscribe.rs index f4dda994f..d1c57a886 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/subscribe.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/subscribe.rs @@ -1,6 +1,8 @@ +use std::net::Ipv4Addr; + use crate::{ commands::{ - globalstate::get::GetGlobalStateCommand, multicastgroup::get::GetMulticastGroupCommand, + accesspass::get::GetAccessPassCommand, multicastgroup::get::GetMulticastGroupCommand, user::get::GetUserCommand, }, DoubleZeroClient, @@ -15,6 +17,7 @@ use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Signature} #[derive(Debug, PartialEq, Clone)] pub struct SubscribeMulticastGroupCommand { pub group_pk: Pubkey, + pub client_ip: Ipv4Addr, pub user_pk: Pubkey, pub publisher: bool, pub subscriber: bool, @@ -22,10 +25,6 @@ pub struct SubscribeMulticastGroupCommand { impl SubscribeMulticastGroupCommand { pub fn execute(&self, client: &dyn DoubleZeroClient) -> eyre::Result { - let (globalstate_pubkey, _globalstate) = GetGlobalStateCommand - .execute(client) - .map_err(|_err| eyre::eyre!("Globalstate not initialized"))?; - let (_, mgroup) = GetMulticastGroupCommand { pubkey_or_code: self.group_pk.to_string(), } @@ -35,12 +34,6 @@ impl SubscribeMulticastGroupCommand { if mgroup.status != MulticastGroupStatus::Activated { eyre::bail!("MulticastGroup not active"); } - if self.publisher && !mgroup.pub_allowlist.contains(&client.get_payer()) { - eyre::bail!("Publisher not allowed"); - } - if self.subscriber && !mgroup.sub_allowlist.contains(&client.get_payer()) { - eyre::bail!("Subscriber not allowed"); - } let (_, user) = GetUserCommand { pubkey: self.user_pk, @@ -52,15 +45,30 @@ impl SubscribeMulticastGroupCommand { eyre::bail!("User not active"); } + let (accesspass_pubkey, accesspass) = GetAccessPassCommand { + client_ip: self.client_ip, + user_payer: user.owner, + } + .execute(client) + .map_err(|_err| eyre::eyre!("AccessPass not found"))?; + + if self.publisher && !accesspass.mgroup_pub_allowlist.contains(&self.group_pk) { + eyre::bail!("User not allowed to publish multicast group"); + } + if self.subscriber && !accesspass.mgroup_sub_allowlist.contains(&self.group_pk) { + eyre::bail!("User not allowed to subscribe multicast group"); + } + client.execute_transaction( DoubleZeroInstruction::SubscribeMulticastGroup(MulticastGroupSubscribeArgs { publisher: self.publisher, subscriber: self.subscriber, + client_ip: user.client_ip, }), vec![ AccountMeta::new(self.group_pk, false), + AccountMeta::new(accesspass_pubkey, false), AccountMeta::new(self.user_pk, false), - AccountMeta::new(globalstate_pubkey, false), ], ) } @@ -75,7 +83,7 @@ mod tests { use doublezero_program_common::types::NetworkV4; use doublezero_serviceability::{ instructions::DoubleZeroInstruction, - pda::{get_globalstate_pda, get_location_pda}, + pda::{get_accesspass_pda, get_multicastgroup_pda}, processors::multicastgroup::subscribe::MulticastGroupSubscribeArgs, state::{ accountdata::AccountData, @@ -92,8 +100,7 @@ mod tests { fn test_commands_multicastgroup_subscribe_command() { let mut client = create_test_client(); - let (globalstate_pubkey, _globalstate) = get_globalstate_pda(&client.get_program_id()); - let (pda_pubkey, _bump_seed) = get_location_pda(&client.get_program_id(), 1); + let (mgroup_pubkey, _bump_seed) = get_multicastgroup_pda(&client.get_program_id(), 1); let mgroup = MulticastGroup { account_type: AccountType::MulticastGroup, owner: client.get_payer(), @@ -102,19 +109,19 @@ mod tests { code: "test".to_string(), max_bandwidth: 1000, status: MulticastGroupStatus::Activated, - pub_allowlist: vec![client.get_payer()], - sub_allowlist: vec![client.get_payer()], tenant_pk: Pubkey::default(), multicast_ip: "223.0.0.1".parse().unwrap(), - publishers: vec![], - subscribers: vec![], + publisher_count: 0, + subscriber_count: 0, }; client .expect_get() - .with(predicate::eq(pda_pubkey)) + .with(predicate::eq(mgroup_pubkey)) .returning(move |_| Ok(AccountData::MulticastGroup(mgroup.clone()))); + let client_ip = Ipv4Addr::new(192, 168, 1, 10); + let user_pubkey = Pubkey::new_unique(); let user = User { account_type: AccountType::User, @@ -123,10 +130,10 @@ mod tests { index: 1, tenant_pk: Pubkey::default(), user_type: UserType::Multicast, - device_pk: pda_pubkey, + device_pk: mgroup_pubkey, cyoa_type: UserCYOA::GREOverDIA, - client_ip: Ipv4Addr::UNSPECIFIED, - dz_ip: Ipv4Addr::UNSPECIFIED, + client_ip, + dz_ip: client_ip, tunnel_id: 0, tunnel_net: NetworkV4::default(), status: UserStatus::Activated, @@ -134,6 +141,30 @@ mod tests { subscribers: vec![], validator_pubkey: Pubkey::default(), }; + + let (accesspass_pubkey, _) = get_accesspass_pda( + &client.get_program_id(), + &user.client_ip, + &client.get_payer(), + ); + let accesspass = doublezero_serviceability::state::accesspass::AccessPass { + account_type: AccountType::AccessPass, + bump_seed: 0, + accesspass_type: doublezero_serviceability::state::accesspass::AccessPassType::Prepaid, + client_ip: user.client_ip, + user_payer: client.get_payer(), + last_access_epoch: 0, + connection_count: 0, + status: doublezero_serviceability::state::accesspass::AccessPassStatus::Requested, + owner: client.get_payer(), + mgroup_pub_allowlist: vec![mgroup_pubkey], + mgroup_sub_allowlist: vec![mgroup_pubkey], + }; + client + .expect_get() + .with(predicate::eq(accesspass_pubkey)) + .returning(move |_| Ok(AccountData::AccessPass(accesspass.clone()))); + client .expect_get() .with(predicate::eq(user_pubkey)) @@ -144,21 +175,23 @@ mod tests { .with( predicate::eq(DoubleZeroInstruction::SubscribeMulticastGroup( MulticastGroupSubscribeArgs { + client_ip, publisher: true, subscriber: false, }, )), predicate::eq(vec![ - AccountMeta::new(pda_pubkey, false), + AccountMeta::new(mgroup_pubkey, false), + AccountMeta::new(accesspass_pubkey, false), AccountMeta::new(user_pubkey, false), - AccountMeta::new(globalstate_pubkey, false), ]), ) .returning(|_, _| Ok(Signature::new_unique())); let res = SubscribeMulticastGroupCommand { - group_pk: pda_pubkey, + group_pk: mgroup_pubkey, user_pk: user_pubkey, + client_ip, publisher: true, subscriber: false, } diff --git a/smartcontract/sdk/rs/src/commands/multicastgroup/update.rs b/smartcontract/sdk/rs/src/commands/multicastgroup/update.rs index a1d5178b4..5d1f348a8 100644 --- a/smartcontract/sdk/rs/src/commands/multicastgroup/update.rs +++ b/smartcontract/sdk/rs/src/commands/multicastgroup/update.rs @@ -13,6 +13,8 @@ pub struct UpdateMulticastGroupCommand { pub code: Option, pub multicast_ip: Option, pub max_bandwidth: Option, + pub publisher_count: Option, + pub subscriber_count: Option, } impl UpdateMulticastGroupCommand { @@ -32,6 +34,8 @@ impl UpdateMulticastGroupCommand { code, multicast_ip: self.multicast_ip, max_bandwidth: self.max_bandwidth, + publisher_count: self.publisher_count, + subscriber_count: self.subscriber_count, }), vec![ AccountMeta::new(self.pubkey, false), @@ -70,6 +74,8 @@ mod tests { code: Some("test_group".to_string()), multicast_ip: Some("127.0.0.1".parse().unwrap()), max_bandwidth: Some(1000), + publisher_count: Some(10), + subscriber_count: Some(100), }, )), predicate::eq(vec![ @@ -84,6 +90,8 @@ mod tests { code: Some("test_group".to_string()), multicast_ip: Some("127.0.0.1".parse().unwrap()), max_bandwidth: Some(1000), + publisher_count: Some(10), + subscriber_count: Some(100), }; let update_invalid_command = UpdateMulticastGroupCommand { diff --git a/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs b/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs index 40310b31e..0a8dd0506 100644 --- a/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs +++ b/smartcontract/sdk/rs/src/commands/user/create_subscribe.rs @@ -43,12 +43,6 @@ impl CreateSubscribeUserCommand { if mgroup.status != MulticastGroupStatus::Activated { eyre::bail!("MulticastGroup not active"); } - if self.publisher && !mgroup.pub_allowlist.contains(&client.get_payer()) { - eyre::bail!("Publisher not allowed"); - } - if self.subscriber && !mgroup.sub_allowlist.contains(&client.get_payer()) { - eyre::bail!("Subscriber not allowed"); - } let (accesspass_pk, _) = get_accesspass_pda( &client.get_program_id(), diff --git a/smartcontract/sdk/rs/src/commands/user/delete.rs b/smartcontract/sdk/rs/src/commands/user/delete.rs index a0417ad78..064127022 100644 --- a/smartcontract/sdk/rs/src/commands/user/delete.rs +++ b/smartcontract/sdk/rs/src/commands/user/delete.rs @@ -32,6 +32,7 @@ impl DeleteUserCommand { SubscribeMulticastGroupCommand { group_pk: *mgroup_pk, user_pk: self.pubkey, + client_ip: user.client_ip, publisher: false, subscriber: false, } diff --git a/smartcontract/test/start-test.sh b/smartcontract/test/start-test.sh index 21ef82e20..089d1f2b3 100644 --- a/smartcontract/test/start-test.sh +++ b/smartcontract/test/start-test.sh @@ -190,46 +190,24 @@ echo "Creating multicast groups" echo "Add me to multicast group allowlist" -./target/doublezero multicast group allowlist subscriber add --code mg01 --pubkey me -./target/doublezero multicast group allowlist subscriber add --code mg02 --pubkey me -./target/doublezero multicast group allowlist subscriber add --code mg03 --pubkey me -./target/doublezero multicast group allowlist publisher add --code mg01 --pubkey me -./target/doublezero multicast group allowlist publisher add --code mg02 --pubkey me -./target/doublezero multicast group allowlist publisher add --code mg03 --pubkey me +./target/doublezero multicast group allowlist subscriber add --code mg01 --user-payer me --client-ip 100.0.0.5 +./target/doublezero multicast group allowlist subscriber add --code mg01 --user-payer me --client-ip 100.0.0.6 + +./target/doublezero multicast group allowlist subscriber add --code mg02 --user-payer me --client-ip 100.0.0.5 +./target/doublezero multicast group allowlist subscriber add --code mg03 --user-payer me --client-ip 100.0.0.5 + +./target/doublezero multicast group allowlist publisher add --code mg01 --user-payer me --client-ip 100.0.0.6 +./target/doublezero multicast group allowlist publisher add --code mg02 --user-payer me --client-ip 100.0.0.6 +./target/doublezero multicast group allowlist publisher add --code mg03 --user-payer me --client-ip 100.0.0.6 echo "Creating multicast user & subscribe" ./target/doublezero user create-subscribe --device ty2-dz01 --client-ip 100.0.0.5 --subscriber mg01 -w ./target/doublezero user create-subscribe --device ty2-dz01 --client-ip 100.0.0.6 --subscriber mg01 -w -./target/doublezero user subscribe --user EthThV5iWtvrcM9G9qXQCmg2sNERLdfxt6AVKuhAewZF --group mg01 --publisher -w -./target/doublezero user subscribe --user EthThV5iWtvrcM9G9qXQCmg2sNERLdfxt6AVKuhAewZF --group mg02 --publisher -w -./target/doublezero user subscribe --user vwHPjLfH7aU4G2vDBAqV3on5WQgXLEKq67kNw7Q5Mos --group mg01 --subscriber -w - -echo "########################################################################" - -exit 0 - -echo "Delete users" -./target/doublezero user delete --pubkey Do1iXv6tNMHRzF1yYHBcLNfNngCK6Yyr9izpLZc1rrwW -./target/doublezero user delete --pubkey J2MUYJeJvTfrHpxMm3tVYkcDhTwgAFFju2veS27WhByX - -echo "Delete tunnels" -./target/doublezero link delete --pubkey 47Z31KgJW1A7HYar7XGrb6Ax8x2d53ZL3RmcY9ofViet -./target/doublezero link delete --pubkey 8k1uzVNaUjiTvkYe7huBqUXgDvDYa5rEbes4sJBwRf9P -./target/doublezero link delete --pubkey 2jH9iDKb8BjSgyQD7t7gfbtNDCPU9WDpngKbwpoUB8YC - -./target/doublezero link delete --pubkey Cv2rR6dyRpjjTXQjqDrNA8j2HycthusJgihPrJJFj7pn -./target/doublezero link delete --pubkey CeteLjdtNZW7EYVYNK7JEMB1dkgk5wqtEUCCiiic7egt +./target/doublezero user subscribe --user vwHPjLfH7aU4G2vDBAqV3on5WQgXLEKq67kNw7Q5Mos --group mg01 --publisher -w +./target/doublezero user subscribe --user vwHPjLfH7aU4G2vDBAqV3on5WQgXLEKq67kNw7Q5Mos --group mg02 --publisher -w -echo "Delete devices" -./target/doublezero device delete --pubkey 3TD6MDfCo2mVeR9a71ukrdXBYVLyWz5cz8RLcNojVpcv -./target/doublezero device delete --pubkey FBUy8tzFWa8LhQmCfXbnWZMg1XUDQfudanoVK5NP4KGP -echo "Delete exchanges" -./target/doublezero exchange delete --pubkey EXFf8KgN5C22EP3ufmscFGNmNzVtTm2HrppBMSn3sn3G -./target/doublezero exchange delete --pubkey EpE1QxRzUXFLSAPKcsGrHrdareBZ7hNsyJtTPw1iL7q8 - -echo "Delete locations" -./target/doublezero location delete --pubkey XEY7fFCJ8r1FM9xwyvMqZ3GEgEbKNBTw65N2ynGJXRD -./target/doublezero location delete --pubkey 3rHsZ8d3oSRfcz5S1MWGNk3hmaMKMKNGerTkWzuNpwu9 +echo "########################################################################" +echo "Setup complete" \ No newline at end of file From f6378ca6b49be0a1d9c21dd73befd82e0d85678a Mon Sep 17 00:00:00 2001 From: Juan Olveira Date: Thu, 2 Oct 2025 21:25:12 +0000 Subject: [PATCH 2/8] Remove redundant IP lookup from multicast allowlist requests --- e2e/qa_test.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/e2e/qa_test.go b/e2e/qa_test.go index 9089ca7c4..60e9d2718 100644 --- a/e2e/qa_test.go +++ b/e2e/qa_test.go @@ -254,15 +254,10 @@ func TestConnectivityMulticast(t *testing.T) { client, err := getQAClient(publisher) require.NoError(t, err, "Failed to create QA client") - ips, err := net.LookupIP(publisher) - require.NoError(t, err, "Failed to lookup IP for publisher") - ownerIP := ips[0].String() - req := &pb.MulticastAllowListAddRequest{ - Mode: pb.MulticastAllowListAddRequest_PUBLISHER, - Code: code, - Pubkey: ownerPubKey, - ClientIp: ownerIP, + Mode: pb.MulticastAllowListAddRequest_PUBLISHER, + Code: code, + Pubkey: ownerPubKey, } result, err := client.MulticastAllowListAdd(ctx, req) require.NoError(t, err, "MulticastAllowListAdd failed") @@ -272,10 +267,9 @@ func TestConnectivityMulticast(t *testing.T) { t.Logf("Multicast group %s added to allow list for publisher %s", code, ownerPubKey) req = &pb.MulticastAllowListAddRequest{ - Mode: pb.MulticastAllowListAddRequest_SUBSCRIBER, - Code: code, - Pubkey: ownerPubKey, - ClientIp: ownerIP, + Mode: pb.MulticastAllowListAddRequest_SUBSCRIBER, + Code: code, + Pubkey: ownerPubKey, } result, err = client.MulticastAllowListAdd(ctx, req) require.NoError(t, err, "MulticastAllowListAdd failed") From c55f3cda1b24699f28ddf13ad34c10ea5a934288 Mon Sep 17 00:00:00 2001 From: Juan Olveira Date: Thu, 2 Oct 2025 21:52:44 +0000 Subject: [PATCH 3/8] Update MulticastAllowListAdd to use --user-payer instead of --pubkey --- e2e/internal/rpc/agent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/internal/rpc/agent.go b/e2e/internal/rpc/agent.go index 83d351b3a..3e1a17353 100644 --- a/e2e/internal/rpc/agent.go +++ b/e2e/internal/rpc/agent.go @@ -386,7 +386,7 @@ func (q *QAAgent) MulticastAllowListAdd(ctx context.Context, req *pb.MulticastAl } q.log.Info("Received MulticastAllowListAdd request", "pubkey", req.GetPubkey(), "client-ip", ipStr, "code", req.GetCode(), "mode", mode) - cmd := exec.Command("doublezero", "multicast", "group", "allowlist", mode, "add", "--pubkey", req.GetPubkey(), "--client-ip", ipStr, "--code", req.GetCode()) + cmd := exec.Command("doublezero", "multicast", "group", "allowlist", mode, "add", "--user-payer", req.GetPubkey(), "--client-ip", ipStr, "--code", req.GetCode()) result, err := runCmd(cmd) if err != nil { q.log.Error("Failed to add multicast allowlist entry", "error", err, "output", result.Output) From 9e367bd6a75910ae4f4e7ebcf484edadbe28f34e Mon Sep 17 00:00:00 2001 From: "jolveira@gmail.com" Date: Mon, 6 Oct 2025 20:10:54 +0000 Subject: [PATCH 4/8] Enhance multicast allowlist functionality to include user payer and client IP in logs and display --- e2e/qa_test.go | 28 +++++++---- smartcontract/cli/src/multicastgroup/get.rs | 56 ++++++++++++++++++--- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/e2e/qa_test.go b/e2e/qa_test.go index 60e9d2718..fed02abb1 100644 --- a/e2e/qa_test.go +++ b/e2e/qa_test.go @@ -259,24 +259,30 @@ func TestConnectivityMulticast(t *testing.T) { Code: code, Pubkey: ownerPubKey, } + result, err := client.MulticastAllowListAdd(ctx, req) require.NoError(t, err, "MulticastAllowListAdd failed") if result.GetSuccess() == false || result.GetReturnCode() != 0 { require.Fail(t, "MulticastAllowListAdd failed", result.GetOutput()) } - t.Logf("Multicast group %s added to allow list for publisher %s", code, ownerPubKey) + t.Logf("Multicast group %s added to allow list for publisher server: %s user-payer: %s", code, publisher, ownerPubKey) - req = &pb.MulticastAllowListAddRequest{ - Mode: pb.MulticastAllowListAddRequest_SUBSCRIBER, - Code: code, - Pubkey: ownerPubKey, - } - result, err = client.MulticastAllowListAdd(ctx, req) - require.NoError(t, err, "MulticastAllowListAdd failed") - if result.GetSuccess() == false || result.GetReturnCode() != 0 { - require.Fail(t, "MulticastAllowListAdd failed", result.GetOutput()) + for _, subscriber := range subscribers { + client, err := getQAClient(subscriber) + require.NoError(t, err, "Failed to create QA client") + + req = &pb.MulticastAllowListAddRequest{ + Mode: pb.MulticastAllowListAddRequest_SUBSCRIBER, + Code: code, + Pubkey: ownerPubKey, + } + result, err = client.MulticastAllowListAdd(ctx, req) + require.NoError(t, err, "MulticastAllowListAdd failed") + if result.GetSuccess() == false || result.GetReturnCode() != 0 { + require.Fail(t, "MulticastAllowListAdd failed", result.GetOutput()) + } + t.Logf("Multicast group %s added to allow list for subscriber server: %s user-payer: %s", code, subscriber, ownerPubKey) } - t.Logf("Multicast group %s added to allow list for subscriber %s", code, ownerPubKey) }) { t.Fatal("Failed to update multicast allow list") } diff --git a/smartcontract/cli/src/multicastgroup/get.rs b/smartcontract/cli/src/multicastgroup/get.rs index fc1c94bd9..efc9d2a7f 100644 --- a/smartcontract/cli/src/multicastgroup/get.rs +++ b/smartcontract/cli/src/multicastgroup/get.rs @@ -1,12 +1,16 @@ use crate::{doublezerocommand::CliCommand, validators::validate_pubkey_or_code}; use clap::Args; +use doublezero_program_common::serializer; use doublezero_program_common::types::parse_utils::bandwidth_to_string; use doublezero_sdk::commands::{ - device::list::ListDeviceCommand, location::list::ListLocationCommand, - multicastgroup::get::GetMulticastGroupCommand, user::list::ListUserCommand, + accesspass::list::ListAccessPassCommand, device::list::ListDeviceCommand, + location::list::ListLocationCommand, multicastgroup::get::GetMulticastGroupCommand, + user::list::ListUserCommand, }; -use std::io::Write; -use tabled::{builder::Builder, settings::Style}; +use serde::Serialize; +use solana_sdk::pubkey::Pubkey; +use std::{io::Write, net::Ipv4Addr}; +use tabled::{builder::Builder, settings::Style, Table, Tabled}; #[derive(Args, Debug)] pub struct GetMulticastGroupCliCommand { @@ -15,6 +19,16 @@ pub struct GetMulticastGroupCliCommand { pub code: String, } +#[derive(Tabled, Serialize)] +pub struct MulticastAllowlistDisplay { + #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] + pub account: Pubkey, + pub mode: String, + pub client_ip: Ipv4Addr, + #[serde(serialize_with = "serializer::serialize_pubkey_as_string")] + pub user_payer: Pubkey, +} + impl GetMulticastGroupCliCommand { pub fn execute(self, client: &C, out: &mut W) -> eyre::Result<()> { let (mgroup_pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { @@ -27,7 +41,7 @@ impl GetMulticastGroupCliCommand { // Write the multicast group details first writeln!(out, - "account: {}\r\ncode: {}\r\nmulticast_ip: {}\r\nmax_bandwidth: {}\r\nstatus: {}\r\nowner: {}\r\n\r\nusers:\r\n", + "account: {}\r\ncode: {}\r\nmulticast_ip: {}\r\nmax_bandwidth: {}\r\nstatus: {}\r\nowner: {}", mgroup_pubkey, mgroup.code, &mgroup.multicast_ip, @@ -36,6 +50,36 @@ impl GetMulticastGroupCliCommand { mgroup.owner )?; + let list_accesspass = client.list_accesspass(ListAccessPassCommand {})?; + + let mga_displays = list_accesspass + .into_iter() + .filter(|(_, accesspass)| { + accesspass.mgroup_sub_allowlist.contains(&mgroup_pubkey) + || accesspass.mgroup_pub_allowlist.contains(&mgroup_pubkey) + }) + .map(|(_, accesspass)| MulticastAllowlistDisplay { + account: mgroup_pubkey, + mode: if accesspass.mgroup_pub_allowlist.contains(&mgroup_pubkey) { + if accesspass.mgroup_sub_allowlist.contains(&mgroup_pubkey) { + "P+S".to_string() + } else { + "P".to_string() + } + } else { + "S".to_string() + }, + client_ip: accesspass.client_ip, + user_payer: accesspass.user_payer, + }) + .collect::>(); + + let table = Table::new(mga_displays) + .with(Style::psql().remove_horizontals()) + .to_string(); + + writeln!(out, "\r\nallowlist:\r\n{table}")?; + let mut builder = Builder::default(); builder.push_record([ "account", @@ -103,7 +147,7 @@ impl GetMulticastGroupCliCommand { .with(Style::psql().remove_horizontals()) .to_string(); - writeln!(out, "{table}")?; + writeln!(out, "\r\nusers:\r\n{table}")?; Ok(()) } From 267c40e1a6d8bd9a84697e67d692a7bdc542eccc Mon Sep 17 00:00:00 2001 From: "jolveira@gmail.com" Date: Mon, 6 Oct 2025 20:13:23 +0000 Subject: [PATCH 5/8] Update changelog to reflect breaking change in multicast group allowlist storage --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b213011f..6d0eab2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ### Breaking +- Multicast group change: Regeneration of all multicast group allowlists required, as allowlists are now stored within each Access Pass instead of at the multicast group level. + ### Changes - CLI From 832166ccc3073a3bfd1759997b419e34e1275cf4 Mon Sep 17 00:00:00 2001 From: "jolveira@gmail.com" Date: Mon, 6 Oct 2025 20:25:39 +0000 Subject: [PATCH 6/8] Refactor GetMulticastGroupCliCommand to consolidate imports and update test cases to include access pass details in output --- smartcontract/cli/src/multicastgroup/get.rs | 31 ++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/smartcontract/cli/src/multicastgroup/get.rs b/smartcontract/cli/src/multicastgroup/get.rs index efc9d2a7f..ca7478160 100644 --- a/smartcontract/cli/src/multicastgroup/get.rs +++ b/smartcontract/cli/src/multicastgroup/get.rs @@ -1,7 +1,6 @@ use crate::{doublezerocommand::CliCommand, validators::validate_pubkey_or_code}; use clap::Args; -use doublezero_program_common::serializer; -use doublezero_program_common::types::parse_utils::bandwidth_to_string; +use doublezero_program_common::{serializer, types::parse_utils::bandwidth_to_string}; use doublezero_sdk::commands::{ accesspass::list::ListAccessPassCommand, device::list::ListDeviceCommand, location::list::ListLocationCommand, multicastgroup::get::GetMulticastGroupCommand, @@ -168,6 +167,9 @@ mod tests { get_multicastgroup_pda, AccountType, Device, DeviceStatus, GetLocationCommand, Location, LocationStatus, MulticastGroup, MulticastGroupStatus, User, UserCYOA, UserStatus, UserType, }; + use doublezero_serviceability::state::accesspass::{ + AccessPass, AccessPassStatus, AccessPassType, + }; use mockall::predicate; use solana_sdk::pubkey::Pubkey; @@ -291,6 +293,27 @@ mod tests { Ok(users) }); + client.expect_list_accesspass().returning(move |_| { + let mut accesspasses = std::collections::HashMap::new(); + accesspasses.insert( + Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1"), + AccessPass { + account_type: AccountType::AccessPass, + bump_seed: 255, + accesspass_type: AccessPassType::Prepaid, + last_access_epoch: u64::MAX, + connection_count: 0, + user_payer: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1"), + client_ip: [192, 168, 1, 1].into(), + mgroup_pub_allowlist: vec![mgroup_pubkey], + mgroup_sub_allowlist: vec![mgroup_pubkey], + owner: Pubkey::from_str_const("11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1"), + status: AccessPassStatus::Requested, + }, + ); + Ok(accesspasses) + }); + let multicastgroup2 = multicastgroup.clone(); client .expect_get_multicastgroup() @@ -324,7 +347,7 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok(), "I should find a item by pubkey"); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\n\r\nusers:\r\n\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | P | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); + assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\n\r\nallowlist:\r\n account | mode | client_ip | user_payer \n G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj | P+S | 192.168.1.1 | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n\r\nusers:\r\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | P | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); // Expected success let mut output = Vec::new(); @@ -334,6 +357,6 @@ mod tests { .execute(&client, &mut output); assert!(res.is_ok(), "I should find a item by code"); let output_str = String::from_utf8(output).unwrap(); - assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\n\r\nusers:\r\n\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | P | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); + assert_eq!(output_str, "account: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\r\ncode: test\r\nmulticast_ip: 10.0.0.1\r\nmax_bandwidth: 1Gbps\r\nstatus: activated\r\nowner: G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj\n\r\nallowlist:\r\n account | mode | client_ip | user_payer \n G4DjGHreV54t5yeNuSHi5iVcT5Qkykuj43pWWdSsP3dj | P+S | 192.168.1.1 | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n\r\nusers:\r\n account | multicast_mode | device | location | cyoa_type | client_ip | tunnel_id | tunnel_net | dz_ip | status | owner \n 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 | P | 11111111111111111111111111111111 | | GREOverDIA | 192.168.1.1 | 12345 | 10.0.0.0/32 | 10.0.0.2 | activated | 11111115q4EpJaTXAZWpCg3J2zppWGSZ46KXozzo1 \n"); } } From b1bcd8c90fc914e62c18aa533629efb54802e404 Mon Sep 17 00:00:00 2001 From: "jolveira@gmail.com" Date: Mon, 6 Oct 2025 21:46:16 +0000 Subject: [PATCH 7/8] Update changelog to include multicast group memberships in AccessPass listings for improved visibility --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0eab2a0..78d648dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,13 @@ All notable changes to this project will be documented in this file. - CLI - Added a wait in the `disconnect` command to ensure the account is fully closed before returning, preventing failures during rapid disconnect/reconnect sequences. + - Display multicast group memberships (publisher/subscriber) in AccessPass listings to improve visibility. - Activator - Reduce logging noise when processing snapshot events - Wrap main select handler in loop to avoid shutdown on branch error - Onchain programs - Remove user-level allowlist management from CLI and admin interfaces; manage multicast group allowlists through AccessPass. - Add Validate trait for core types (AccessPass, Contributor, Interface, etc.) and enforce runtime checks before account operations. - - Internet telemetry - Add circuit label to metrics; create a new metric for missing circuit samples - Create a new metric that tracks how long it takes collector tasks to run @@ -125,8 +125,6 @@ All notable changes to this project will be documented in this file. - Updated unit tests and e2e tests to validate the new initialization and activation flow. - Contributor Operations - Contributors must explicitly run device update to set a valid max_users and activate a Device. -- CLI - - Display multicast group memberships (publisher/subscriber) in AccessPass listings to improve visibility. ## [v0.6.2](https://github.com/malbeclabs/doublezero/compare/client/v0.6.0...client/v0.6.2) – 2025-09-02 From 434dae7e6cca3ec81050545f092d8915727dc952 Mon Sep 17 00:00:00 2001 From: "jolveira@gmail.com" Date: Tue, 7 Oct 2025 14:20:07 +0000 Subject: [PATCH 8/8] Add AccessPass class and documentation to manage network access and permissions --- .../doublezero-serviceability/README.md | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/smartcontract/programs/doublezero-serviceability/README.md b/smartcontract/programs/doublezero-serviceability/README.md index 6fbfc30a2..9ce28e7f9 100644 --- a/smartcontract/programs/doublezero-serviceability/README.md +++ b/smartcontract/programs/doublezero-serviceability/README.md @@ -94,6 +94,17 @@ classDiagram String side_a_iface_name String side_z_iface_name } + class AccessPass { + AccountType account_type + Pubkey owner + u8 bump_seed + AccessPassType accesspass_type + IpV4 client_ip + Pubkey user_payer + u64 last_access_epoch + u16 connection_count + AccessPassStatus status + } class User { AccountType account_type Pubkey owner @@ -106,8 +117,6 @@ classDiagram Ipv4Addr dz_ip u16 tunnel_id NetworkV4 tunnel_net - Pubkey[] publisher - Pubkey[] subscribers UserStatus status } class MulticastGroup { @@ -118,10 +127,8 @@ classDiagram Pubkey tenant_pk String code MulticastGroupStatus status - Pubkey[] pub_allowlist - Pubkey[] sub_allowlist - Pubkey[] publisher - Pubkey[] subscribers + u32 publisher_count + u32 subscriber_count } class GlobalConfig { AccountType account_type @@ -146,6 +153,8 @@ classDiagram Link --> Device : side_a_pk Link --> Device : side_z_pk User --> Device : device_pk + AccessPass --> MulticastGroup: mgroup_pub_allowlist + AccessPass --> MulticastGroup: mgroup_sub_allowlist User --> MulticastGroup : publishers User --> MulticastGroup : subscribers MulticastGroup --> User : publishers @@ -340,6 +349,35 @@ stateDiagram-v2 | side_a_iface_name | String | Side A interface name | | side_z_iface_name | String | Side Z interface name | +## AccessPass + +Represents an access pass in the DoubleZero network. AccessPasses are used to control and track access to network resources, including multicast group permissions and connection status. + +```mermaid +stateDiagram-v2 + [*] --> Requested + Requested --> Connected: connect + Connected --> Disconnected: disconnect + Connected --> Expired: expire + Disconnected --> Connected: reconnect + Disconnected --> Expired: expire + Expired --> [*] +``` + +| Field | Type | Description | +|------------------------|---------------------|--------------------------------------------------------------------| +| account_type | AccountType | Type of account (must be AccessPass) | +| owner | Pubkey | Pass owner | +| bump_seed | u8 | Bump seed for PDA | +| accesspass_type | AccessPassType | Pass type (`Prepaid` or `SolanaValidator(Pubkey)`) | +| client_ip | Ipv4Addr | Associated client IP | +| user_payer | Pubkey | User who pays for the access | +| last_access_epoch | u64 | Last access epoch (`u64::MAX` = unlimited) | +| connection_count | u16 | Number of performed connections | +| status | AccessPassStatus | Pass status (`Requested`, `Connected`, `Disconnected`, `Expired`) | +| mgroup_pub_allowlist | Vec | Multicast groups this pass can publish to | +| mgroup_sub_allowlist | Vec | Multicast groups this pass can subscribe to | + ## User Represents a user account in the DoubleZero network. Users are associated with devices and tunnels, and store information such as user type, IP addresses, and status.