diff --git a/psyche-book/src/enduser/authentication.md b/psyche-book/src/enduser/authentication.md index 42916048f..5cb65a1cf 100644 --- a/psyche-book/src/enduser/authentication.md +++ b/psyche-book/src/enduser/authentication.md @@ -20,7 +20,7 @@ A Training run can be configured to be restricted to only a set of whitelisted k ## Permissionless Runs -Permissionless runs are open to anyone without any `authorization` required. The owner of the run can set this for a run when creating it. This type of authorization can be made by creating an `authorization` with a special `authorizer` valid for everyone: `11111111111111111111111111111111` +Permissionless runs are open to anyone without any `authorization` required. The owner of the run can set this for a run when creating it. This type of authorization can be made by using the `permissionless` authorization type, which maps to the system program ID (`11111111111111111111111111111111`). A CLI is provided for this: @@ -28,7 +28,7 @@ A CLI is provided for this: run-manager join-authorization-create \ --rpc [RPC] \ --wallet-private-key-path [JOIN_AUTHORITY_KEYPAIR_FILE] \ - --authorizer 11111111111111111111111111111111 + --authorization permissionless ``` ## Permissioned Runs @@ -49,7 +49,7 @@ For the `join_authority` to issues new `authorization`, a CLI is provided: run-manager join-authorization-create \ --rpc [RPC] \ --wallet-private-key-path [JOIN_AUTHORITY_KEYPAIR_FILE] \ - --authorizer [USER_MASTER_PUBKEY] + --authorization [USER_MASTER_PUBKEY] ``` For the `authorizer` to then set a list of delegate, the following CLI is provided: @@ -69,7 +69,7 @@ Removing the authorization is also possible through CLI: run-manager join-authorization-delete \ --rpc [RPC] \ --wallet-private-key-path [JOIN_AUTHORITY_KEYPAIR_FILE] \ - --authorizer [USER_MASTER_PUBKEY] + --authorization [USER_MASTER_PUBKEY] ``` ## Further information diff --git a/psyche-book/src/enduser/create-run.md b/psyche-book/src/enduser/create-run.md index 7a9354ca1..34ce404fc 100644 --- a/psyche-book/src/enduser/create-run.md +++ b/psyche-book/src/enduser/create-run.md @@ -22,13 +22,13 @@ We’ll need a keypair file that manages join permissions. This can be the defau #### Join Authority for Public Runs -If we're looking to make a permissionless run (anyone can join), we'll need to create an authorization that's valid for everyone. In this case, if we set the authorizer to be `11111111111111111111111111111111` in the following command it will be valid for everyone so any other client can join the created run without additional restrictions. +If we're looking to make a permissionless run (anyone can join), we'll need to create an authorization that's valid for everyone. This can be done by using the `permissionless` authorization type, which maps to the system program ID (`11111111111111111111111111111111`) and allows any client to join the created run without additional restrictions. ```sh run-manager join-authorization-create \ --rpc [RPC] \ --wallet-private-key-path [SOLANA_KEY_FILE] \ - --authorizer 11111111111111111111111111111111 + --authorization permissionless ``` In this case the `SOLANA_KEY_FILE` needs to be the path to a Solana keypair that is creating the authorization for this specific run. @@ -45,7 +45,7 @@ First, create the authorization with the following parameters: run-manager join-authorization-create \ --rpc [RPC] \ --wallet-private-key-path ~/.config/solana/owner.json \ - --authorizer $(solana-keygen pubkey ~/.config/solana/joiner.json) + --authorization $(solana-keygen pubkey ~/.config/solana/joiner.json) ``` This command uses the public key of the user you want to allow to join and the keypair of the run owner to create the appropriate authorization. The `solana-keygen pubkey` command just gives you the public key derivated from the keypair file. diff --git a/scripts/create-permissionless-run.sh b/scripts/create-permissionless-run.sh index 582974fff..5bce1db5e 100755 --- a/scripts/create-permissionless-run.sh +++ b/scripts/create-permissionless-run.sh @@ -43,7 +43,7 @@ cargo run --release --bin run-manager -- \ join-authorization-create \ --wallet-private-key-path ${WALLET_FILE} \ --rpc "${RPC}" \ - --authorizer 11111111111111111111111111111111 + --authorization permissionless echo -e "\n[+] Creating training run..." cargo run --release --bin run-manager -- \ diff --git a/scripts/setup-test-run.sh b/scripts/setup-test-run.sh index 3a2a74f22..77849d4e3 100755 --- a/scripts/setup-test-run.sh +++ b/scripts/setup-test-run.sh @@ -45,7 +45,7 @@ nix run .#run-manager -- \ join-authorization-create \ --wallet-private-key-path "${WALLET_FILE}" \ --rpc "${RPC}" \ - --authorizer 11111111111111111111111111111111 + --authorization permissionless echo "[+] Creating run..." nix run .#run-manager -- \ diff --git a/tools/rust-tools/run-manager/src/commands/authorization/create.rs b/tools/rust-tools/run-manager/src/commands/authorization/create.rs index 8a84623c0..ad1de702c 100644 --- a/tools/rust-tools/run-manager/src/commands/authorization/create.rs +++ b/tools/rust-tools/run-manager/src/commands/authorization/create.rs @@ -1,5 +1,5 @@ use crate::commands::Command; -use anchor_client::solana_sdk::pubkey::Pubkey; +use crate::commands::authorization::Authorization; use anyhow::Result; use async_trait::async_trait; use clap::Args; @@ -10,14 +10,15 @@ use psyche_solana_rpc::instructions; #[derive(Debug, Clone, Args)] #[command()] pub struct CommandJoinAuthorizationCreate { + /// Authorization type: either a pubkey address or "permissionless" (maps to system program ID) #[clap(long, env)] - pub authorizer: Pubkey, + pub authorization: Authorization, } #[async_trait] impl Command for CommandJoinAuthorizationCreate { async fn execute(self, backend: SolanaBackend) -> Result<()> { - let Self { authorizer } = self; + let authorizer = self.authorization.to_pubkey(); let payer = backend.get_payer(); let grantor = backend.get_payer(); diff --git a/tools/rust-tools/run-manager/src/commands/authorization/delete.rs b/tools/rust-tools/run-manager/src/commands/authorization/delete.rs index 4a6a99917..5c88bb352 100644 --- a/tools/rust-tools/run-manager/src/commands/authorization/delete.rs +++ b/tools/rust-tools/run-manager/src/commands/authorization/delete.rs @@ -1,5 +1,5 @@ use crate::commands::Command; -use anchor_client::solana_sdk::pubkey::Pubkey; +use crate::commands::authorization::Authorization; use anyhow::Result; use async_trait::async_trait; use clap::Args; @@ -10,14 +10,15 @@ use psyche_solana_rpc::instructions; #[derive(Debug, Clone, Args)] #[command()] pub struct CommandJoinAuthorizationDelete { + /// Authorization type: either a pubkey address or "permissionless" (maps to system program ID) #[clap(long, env)] - pub authorizer: Pubkey, + pub authorization: Authorization, } #[async_trait] impl Command for CommandJoinAuthorizationDelete { async fn execute(self, backend: SolanaBackend) -> Result<()> { - let Self { authorizer } = self; + let authorizer = self.authorization.to_pubkey(); let grantor = backend.get_payer(); let grantee = authorizer; diff --git a/tools/rust-tools/run-manager/src/commands/authorization/mod.rs b/tools/rust-tools/run-manager/src/commands/authorization/mod.rs index aa3ab50e5..33a7d75ee 100644 --- a/tools/rust-tools/run-manager/src/commands/authorization/mod.rs +++ b/tools/rust-tools/run-manager/src/commands/authorization/mod.rs @@ -2,8 +2,10 @@ pub mod create; pub mod delegate; pub mod delete; pub mod read; +pub mod types; pub use create::*; pub use delegate::*; pub use delete::*; pub use read::*; +pub use types::*; diff --git a/tools/rust-tools/run-manager/src/commands/authorization/read.rs b/tools/rust-tools/run-manager/src/commands/authorization/read.rs index b69970d03..c167c80ce 100644 --- a/tools/rust-tools/run-manager/src/commands/authorization/read.rs +++ b/tools/rust-tools/run-manager/src/commands/authorization/read.rs @@ -1,6 +1,6 @@ use crate::commands::Command; +use crate::commands::authorization::Authorization; use anchor_client::solana_sdk::pubkey::Pubkey; -use anchor_client::solana_sdk::system_program; use anyhow::Result; use async_trait::async_trait; use clap::Args; @@ -12,20 +12,16 @@ use psyche_solana_rpc::SolanaBackend; pub struct CommandJoinAuthorizationRead { #[clap(long, env)] pub join_authority: Pubkey, + /// Authorization type: either a pubkey address or "permissionless" (maps to system program ID) #[clap(long, env)] - pub authorizer: Option, + pub authorization: Authorization, } #[async_trait] impl Command for CommandJoinAuthorizationRead { async fn execute(self, backend: SolanaBackend) -> Result<()> { - let Self { - join_authority, - authorizer, - } = self; - - let grantor = join_authority; - let grantee = authorizer.unwrap_or(system_program::ID); + let grantor = self.join_authority; + let grantee = self.authorization.to_pubkey(); let scope = psyche_solana_coordinator::logic::JOIN_RUN_AUTHORIZATION_SCOPE; println!("Authorization Grantor: {}", grantor); diff --git a/tools/rust-tools/run-manager/src/commands/authorization/types.rs b/tools/rust-tools/run-manager/src/commands/authorization/types.rs new file mode 100644 index 000000000..bb65a7a78 --- /dev/null +++ b/tools/rust-tools/run-manager/src/commands/authorization/types.rs @@ -0,0 +1,33 @@ +use anchor_client::solana_sdk::pubkey::Pubkey; +use anchor_client::solana_sdk::system_program; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub enum Authorization { + Address(Pubkey), + Permissionless, +} + +impl Authorization { + /// Convert to Pubkey. Permissionless maps to system_program::ID (11111111...) + pub fn to_pubkey(&self) -> Pubkey { + match self { + Authorization::Address(pubkey) => *pubkey, + Authorization::Permissionless => system_program::ID, + } + } +} + +impl FromStr for Authorization { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + if s.eq_ignore_ascii_case("permissionless") { + Ok(Authorization::Permissionless) + } else { + let pubkey = Pubkey::from_str(s) + .map_err(|e| anyhow::anyhow!("Invalid pubkey '{}': {}", s, e))?; + Ok(Authorization::Address(pubkey)) + } + } +} diff --git a/tools/rust-tools/run-manager/src/commands/can_join.rs b/tools/rust-tools/run-manager/src/commands/can_join.rs index 64a6469dd..5321729fd 100644 --- a/tools/rust-tools/run-manager/src/commands/can_join.rs +++ b/tools/rust-tools/run-manager/src/commands/can_join.rs @@ -1,4 +1,5 @@ use crate::commands::Command; +use crate::commands::authorization::Authorization; use anchor_client::solana_sdk::pubkey::Pubkey; use anyhow::Result; use anyhow::bail; @@ -13,8 +14,9 @@ use psyche_solana_rpc::SolanaBackend; pub struct CommandCanJoin { #[clap(short, long, env)] pub run_id: String, + /// Authorization type: either a pubkey address or "permissionless" (maps to system program ID) #[clap(long, env)] - pub authorizer: Option, + pub authorization: Option, #[clap(long, env, alias = "wallet", alias = "user", value_name = "PUBKEY")] pub address: Pubkey, } @@ -24,10 +26,12 @@ impl Command for CommandCanJoin { async fn execute(self, backend: SolanaBackend) -> Result<()> { let Self { run_id, - authorizer, + authorization, address, } = self; + let authorizer = authorization.map(|auth| auth.to_pubkey()); + let coordinator_instance = psyche_solana_coordinator::find_coordinator_instance(&run_id); let coordinator_instance_state = backend .get_coordinator_instance(&coordinator_instance) diff --git a/tools/rust-tools/run-manager/tests/integration_tests.rs b/tools/rust-tools/run-manager/tests/integration_tests.rs index edb623d39..2fb03dc8a 100644 --- a/tools/rust-tools/run-manager/tests/integration_tests.rs +++ b/tools/rust-tools/run-manager/tests/integration_tests.rs @@ -13,7 +13,7 @@ use psyche_solana_rpc::SolanaBackend; use run_manager::commands::{ Command, authorization::{ - CommandJoinAuthorizationCreate, CommandJoinAuthorizationDelete, + Authorization, CommandJoinAuthorizationCreate, CommandJoinAuthorizationDelete, CommandJoinAuthorizationRead, }, can_join::CommandCanJoin, @@ -263,7 +263,7 @@ async fn test_join_authorization_create_and_read() { .expect("Failed to create backend"); let create_params = CommandJoinAuthorizationCreate { - authorizer: grantee_pubkey, + authorization: Authorization::Address(grantee_pubkey), }; create_params @@ -275,7 +275,7 @@ async fn test_join_authorization_create_and_read() { let read_params = CommandJoinAuthorizationRead { join_authority: grantor_arc.pubkey(), - authorizer: Some(grantee_pubkey), + authorization: Authorization::Address(grantee_pubkey), }; read_params @@ -305,7 +305,7 @@ async fn test_join_authorization_delete() { .expect("Failed to create backend"); let create_params = CommandJoinAuthorizationCreate { - authorizer: grantee_pubkey, + authorization: Authorization::Address(grantee_pubkey), }; create_params @@ -316,7 +316,7 @@ async fn test_join_authorization_delete() { tokio::time::sleep(std::time::Duration::from_millis(500)).await; let delete_params = CommandJoinAuthorizationDelete { - authorizer: grantee_pubkey, + authorization: Authorization::Address(grantee_pubkey), }; delete_params @@ -374,7 +374,7 @@ async fn test_can_join_paused_run() { let can_join_params = CommandCanJoin { run_id: run_id.clone(), - authorizer: None, + authorization: None, address: wallet_arc.pubkey(), }; @@ -425,7 +425,7 @@ async fn test_full_authorization_workflow() { // 2. Create authorization for user let auth_params = CommandJoinAuthorizationCreate { - authorizer: user_pubkey, + authorization: Authorization::Address(user_pubkey), }; auth_params @@ -438,7 +438,7 @@ async fn test_full_authorization_workflow() { // 3. Check can-join for authorized user let can_join_params = CommandCanJoin { run_id: run_id.clone(), - authorizer: Some(user_pubkey), + authorization: Some(Authorization::Address(user_pubkey)), address: user_pubkey, }; @@ -651,7 +651,7 @@ async fn test_join_authorization_delegate() { let grantee_pubkey = grantee_keypair.pubkey(); let create_params = CommandJoinAuthorizationCreate { - authorizer: grantee_pubkey, + authorization: Authorization::Address(grantee_pubkey), }; create_params