diff --git a/architectures/decentralized/solana-client/src/app.rs b/architectures/decentralized/solana-client/src/app.rs index cf46c6d38..262f1849c 100644 --- a/architectures/decentralized/solana-client/src/app.rs +++ b/architectures/decentralized/solana-client/src/app.rs @@ -102,9 +102,13 @@ pub async fn build_app( // Construct RunDownClient using the wallet keypair for signing. // This enables GCS checkpoint upload/download via run-down signed URLs. let run_down_keypair = wallet_keypair.clone(); + let authorization_grantee = authorizer + .unwrap_or_else(|| wallet_keypair.pubkey()) + .to_string(); let run_down_client = Arc::new(RunDownClient::new( p.run_id.clone(), wallet_keypair.pubkey().to_string(), + authorization_grantee, move |msg| run_down_keypair.sign_message(msg).as_ref().to_vec(), )); checkpoint_config.run_down_client = Some(run_down_client); diff --git a/shared/data-provider/src/run_down.rs b/shared/data-provider/src/run_down.rs index adc5c3018..e6082e7ac 100644 --- a/shared/data-provider/src/run_down.rs +++ b/shared/data-provider/src/run_down.rs @@ -19,6 +19,7 @@ pub struct RunDownClient { http: reqwest::Client, run_id: String, wallet_address: String, + authorization_grantee: String, sign_fn: Arc, } @@ -34,12 +35,14 @@ impl RunDownClient { pub fn new( run_id: String, wallet_address: String, + authorization_grantee: String, sign_fn: impl Fn(&[u8]) -> Vec + Send + Sync + 'static, ) -> Self { Self { http: reqwest::Client::new(), run_id, wallet_address, + authorization_grantee, sign_fn: Arc::new(sign_fn), } } @@ -73,6 +76,7 @@ impl RunDownClient { let url = format!("{}/upload/{}", base_url(), self.run_id); let body = serde_json::json!({ "walletAddress": self.wallet_address, + "authorizationGrantee": self.authorization_grantee, "filename": filename, "expiresInSeconds": expires_in_seconds, "nonce": nonce.to_string(), @@ -113,6 +117,7 @@ impl RunDownClient { let url = format!("{}/download/{}", base_url(), self.run_id); let body = serde_json::json!({ "walletAddress": self.wallet_address, + "authorizationGrantee": self.authorization_grantee, "expiresInSeconds": expires_in_seconds, "nonce": nonce.to_string(), }); diff --git a/tools/rust-tools/run-manager/src/commands/run/download_results.rs b/tools/rust-tools/run-manager/src/commands/run/download_results.rs index 8bafcc225..c67cd51dd 100644 --- a/tools/rust-tools/run-manager/src/commands/run/download_results.rs +++ b/tools/rust-tools/run-manager/src/commands/run/download_results.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use crate::commands::Command; +use anchor_client::solana_sdk::pubkey::Pubkey; use anyhow::{Context, Result, bail}; use async_trait::async_trait; use clap::Args; @@ -25,6 +26,9 @@ pub struct CommandDownloadResults { #[clap(long)] pub overwrite: bool, + + #[clap(long, env)] + pub authorizer: Pubkey, } #[async_trait] @@ -35,6 +39,7 @@ impl Command for CommandDownloadResults { output_dir, expires_in_seconds, overwrite, + authorizer, } = self; // Check if output directory exists and is not empty @@ -71,14 +76,22 @@ impl Command for CommandDownloadResults { let nonce: u64 = rand::random(); // Generate signature for the run-down service - let signature_b58 = - run_down_service::generate_signature(&backend, &run_id, expires_in_seconds, nonce); + let wallet_address = backend.get_payer().to_string(); + let signature_b58 = run_down_service::generate_signature( + &backend, + &run_id, + &wallet_address, + expires_in_seconds, + nonce, + ); // Make POST request to the API let client = reqwest::Client::new(); let urls_response = run_down_service::get_download_urls( &client, &run_id, + &wallet_address, + &authorizer.to_string(), &signature_b58, expires_in_seconds, nonce, diff --git a/tools/rust-tools/run-manager/src/commands/run/run_down_service.rs b/tools/rust-tools/run-manager/src/commands/run/run_down_service.rs index 8d1a24d7a..48dab0228 100644 --- a/tools/rust-tools/run-manager/src/commands/run/run_down_service.rs +++ b/tools/rust-tools/run-manager/src/commands/run/run_down_service.rs @@ -2,23 +2,27 @@ use anyhow::{Context, Result, bail}; use psyche_solana_rpc::SolanaBackend; use serde::{Deserialize, Serialize}; -const RUN_DOWN_SERVICE_BASE_URL: &str = "https://run-down.nousresearch.com/v1"; +fn run_down_base_url() -> String { + std::env::var("RUN_DOWN_URL") + .unwrap_or_else(|_| "https://run-down.nousresearch.com/v1".to_string()) +} /// Generate a signed message for the Nous run-down service API /// -/// Creates a message in the format: `nous-run-down-service:{run_id}:{expires_in_seconds}:{nonce}` +/// Creates a message in the format: `nous-run-down-service:{run_id}:{wallet_address}:{expires_in_seconds}:{nonce}` /// and signs it using the provided backend's wallet. /// /// Returns the base58-encoded signature. pub fn generate_signature( backend: &SolanaBackend, run_id: &str, + wallet_address: &str, expires_in_seconds: u64, nonce: u64, ) -> String { let message = format!( - "nous-run-down-service:{}:{}:{}", - run_id, expires_in_seconds, nonce + "nous-run-down-service:{}:{}:{}:{}", + run_id, wallet_address, expires_in_seconds, nonce ); let message_bytes = message.as_bytes(); let signature = backend.sign_message(message_bytes); @@ -60,17 +64,22 @@ pub struct UploadUrlResponse { } /// Get a signed upload URL for a file +#[allow(clippy::too_many_arguments)] pub async fn get_upload_url( client: &reqwest::Client, run_id: &str, + wallet_address: &str, + authorization_grantee: &str, signature: &str, filename: &str, expires_in_seconds: u64, nonce: u64, ) -> Result { - let url = format!("{}/upload/{}", RUN_DOWN_SERVICE_BASE_URL, run_id); + let url = format!("{}/upload/{}", run_down_base_url(), run_id); let body = serde_json::json!({ + "walletAddress": wallet_address, + "authorizationGrantee": authorization_grantee, "filename": filename, "expiresInSeconds": expires_in_seconds, "nonce": nonce.to_string(), @@ -98,13 +107,17 @@ pub struct DownloadUrlsResponse { pub async fn get_download_urls( client: &reqwest::Client, run_id: &str, + wallet_address: &str, + authorization_grantee: &str, signature: &str, expires_in_seconds: u64, nonce: u64, ) -> Result { - let url = format!("{}/download/{}", RUN_DOWN_SERVICE_BASE_URL, run_id); + let url = format!("{}/download/{}", run_down_base_url(), run_id); let body = serde_json::json!({ + "walletAddress": wallet_address, + "authorizationGrantee": authorization_grantee, "expiresInSeconds": expires_in_seconds, "nonce": nonce.to_string(), }); diff --git a/tools/rust-tools/run-manager/src/commands/run/upload_data.rs b/tools/rust-tools/run-manager/src/commands/run/upload_data.rs index 0f1d46203..2c87d63f2 100644 --- a/tools/rust-tools/run-manager/src/commands/run/upload_data.rs +++ b/tools/rust-tools/run-manager/src/commands/run/upload_data.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use anchor_client::solana_sdk::pubkey::Pubkey; use anyhow::{Context, Result, bail}; use async_trait::async_trait; use clap::Args; @@ -25,6 +26,9 @@ pub struct CommandUploadData { /// How long the signed URLs should be valid (in seconds) #[clap(long, env, default_value = "3600")] pub expires_in_seconds: u64, + + #[clap(long, env)] + pub authorizer: Pubkey, } #[async_trait] @@ -34,6 +38,7 @@ impl Command for CommandUploadData { run_id, path, expires_in_seconds, + authorizer, } = self; // Determine base directory for computing relative paths @@ -86,13 +91,21 @@ impl Command for CommandUploadData { let nonce: u64 = rand::random(); // Generate signature for the run-down service - let signature_b58 = - run_down_service::generate_signature(&backend, &run_id, expires_in_seconds, nonce); + let wallet_address = backend.get_payer().to_string(); + let signature_b58 = run_down_service::generate_signature( + &backend, + &run_id, + &wallet_address, + expires_in_seconds, + nonce, + ); // Make POST request to get upload URL let upload_response = run_down_service::get_upload_url( &client, &run_id, + &wallet_address, + &authorizer.to_string(), &signature_b58, relative_path, expires_in_seconds,