diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bbcc2bde..78d648dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,19 @@ 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 - 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 @@ -93,7 +99,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 @@ -121,7 +126,6 @@ All notable changes to this project will be documented in this file. - Contributor Operations - Contributors must explicitly run device update to set a valid max_users and activate a Device. - ## [v0.6.2](https://github.com/malbeclabs/doublezero/compare/client/v0.6.0...client/v0.6.2) – 2025-09-02 ### Breaking 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..3e1a17353 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", "--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) @@ -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..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/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..ca7478160 100644 --- a/smartcontract/cli/src/multicastgroup/get.rs +++ b/smartcontract/cli/src/multicastgroup/get.rs @@ -1,12 +1,15 @@ use crate::{doublezerocommand::CliCommand, validators::validate_pubkey_or_code}; use clap::Args; -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::{ - 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,9 +18,19 @@ 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 (pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + let (mgroup_pubkey, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { pubkey_or_code: self.code, })?; @@ -27,19 +40,45 @@ 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: {}", + 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 )?; + 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", @@ -55,11 +94,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 +105,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 +114,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 +131,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(), ]); } @@ -108,7 +146,7 @@ impl GetMulticastGroupCliCommand { .with(Style::psql().remove_horizontals()) .to_string(); - writeln!(out, "{table}")?; + writeln!(out, "\r\nusers:\r\n{table}")?; Ok(()) } @@ -129,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; @@ -240,12 +281,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 |_| { @@ -254,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() @@ -287,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\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\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(); @@ -297,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\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\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"); } } 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/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. 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