diff --git a/architectures/decentralized/solana-client/src/app.rs b/architectures/decentralized/solana-client/src/app.rs index c541d5e74..fbc4b1f91 100644 --- a/architectures/decentralized/solana-client/src/app.rs +++ b/architectures/decentralized/solana-client/src/app.rs @@ -47,6 +47,7 @@ pub struct App { update_tui_interval: Interval, tx_tui_state: Option>, authorizer: Option, + claimer: Option, metrics: Arc, allowlist: allowlist::AllowDynamic, p2p: NC, @@ -61,6 +62,7 @@ pub struct AppParams { pub backup_clusters: Vec, pub tx_tui_state: Option>, pub authorizer: Option, + pub claimer: Option, pub train_args: TrainArgs, } @@ -72,6 +74,7 @@ pub async fn build_app( backup_clusters, tx_tui_state, authorizer, + claimer, train_args: p, }: AppParams, ) -> Result { @@ -151,6 +154,7 @@ pub async fn build_app( tx_tui_state, update_tui_interval: interval(Duration::from_millis(150)), authorizer, + claimer, allowlist, metrics, p2p, @@ -238,8 +242,9 @@ impl App { .join_run( coordinator_instance_pubkey, coordinator_account, - psyche_core::NodeIdentity::new(signer.to_bytes(), *p2p_identity.as_bytes()), self.authorizer, + psyche_core::NodeIdentity::new(signer.to_bytes(), *p2p_identity.as_bytes()), + self.claimer, ) .await?; info!( @@ -355,8 +360,9 @@ impl App { .join_run( coordinator_instance_pubkey, coordinator_account, - id, self.authorizer, + id, + self.claimer, ) .await?; info!( diff --git a/architectures/decentralized/solana-client/src/main.rs b/architectures/decentralized/solana-client/src/main.rs index 0a1bd573e..814296be9 100644 --- a/architectures/decentralized/solana-client/src/main.rs +++ b/architectures/decentralized/solana-client/src/main.rs @@ -80,8 +80,11 @@ enum Commands { rpc_3: String, #[clap(long, env, default_value_t = String::from(""))] ws_rpc_3: String, + #[clap(long, env)] authorizer: Option, + #[clap(long, env)] + claimer: Option, }, Predownload { #[clap(flatten)] @@ -171,6 +174,7 @@ async fn async_main() -> Result<()> { rpc_3, ws_rpc_3, authorizer, + claimer, } => { psyche_client::prepare_environment(); info!( @@ -254,6 +258,7 @@ async fn async_main() -> Result<()> { cluster: cluster.into(), backup_clusters, authorizer, + claimer, train_args: args, }) .await?; diff --git a/architectures/decentralized/solana-common/src/backend.rs b/architectures/decentralized/solana-common/src/backend.rs index 0f5d3e42f..d0f11485f 100644 --- a/architectures/decentralized/solana-common/src/backend.rs +++ b/architectures/decentralized/solana-common/src/backend.rs @@ -274,8 +274,9 @@ impl SolanaBackend { &self, coordinator_instance: Pubkey, coordinator_account: Pubkey, - id: psyche_core::NodeIdentity, authorizer: Option, + id: psyche_core::NodeIdentity, + claimer: Option, ) -> Result { let coordinator_instance_state = self.get_coordinator_instance(&coordinator_instance).await?; @@ -286,6 +287,7 @@ impl SolanaBackend { &coordinator_account, &authorization, id, + &claimer.unwrap_or(self.get_payer()), ); // TODO (vbrunet) - what was the point of doing specifically a timeout here but not the other TXs ? // We timeout the transaction at 5s max, since internally send() polls Solana until the diff --git a/architectures/decentralized/solana-common/src/instructions.rs b/architectures/decentralized/solana-common/src/instructions.rs index c6c0e28cc..22da2224b 100644 --- a/architectures/decentralized/solana-common/src/instructions.rs +++ b/architectures/decentralized/solana-common/src/instructions.rs @@ -19,15 +19,15 @@ pub fn coordinator_init_coordinator( psyche_solana_coordinator::ID, psyche_solana_coordinator::accounts::InitCoordinatorAccounts { payer: *payer, + authority: *main_authority, coordinator_instance, coordinator_account: *coordinator_account, system_program: system_program::ID, }, psyche_solana_coordinator::instruction::InitCoordinator { params: psyche_solana_coordinator::logic::InitCoordinatorParams { - main_authority: *main_authority, - join_authority: *join_authority, run_id: run_id.to_string(), + join_authority: *join_authority, client_version: client_version.to_string(), }, }, @@ -102,6 +102,7 @@ pub fn coordinator_join_run( coordinator_account: &Pubkey, authorization: &Pubkey, client_id: psyche_core::NodeIdentity, + claimer: &Pubkey, ) -> Instruction { anchor_instruction( psyche_solana_coordinator::ID, @@ -112,7 +113,10 @@ pub fn coordinator_join_run( coordinator_account: *coordinator_account, }, psyche_solana_coordinator::instruction::JoinRun { - params: psyche_solana_coordinator::logic::JoinRunParams { client_id }, + params: psyche_solana_coordinator::logic::JoinRunParams { + client_id, + claimer: *claimer, + }, }, ) } @@ -219,6 +223,26 @@ pub fn coordinator_checkpoint( ) } +pub fn coordinator_set_join_authority( + run_id: &str, + coordinator_account: &Pubkey, + main_authority: &Pubkey, + join_authority: &Pubkey, +) -> Instruction { + let coordinator_instance = psyche_solana_coordinator::find_coordinator_instance(run_id); + anchor_instruction( + psyche_solana_coordinator::ID, + psyche_solana_coordinator::accounts::OwnerCoordinatorAccounts { + authority: *main_authority, + coordinator_instance, + coordinator_account: *coordinator_account, + }, + psyche_solana_coordinator::instruction::SetJoinAuthority { + join_authority: *join_authority, + }, + ) +} + pub fn coordinator_update_client_version( run_id: &str, coordinator_account: &Pubkey, @@ -257,6 +281,7 @@ pub fn treasurer_run_create( psyche_solana_treasurer::ID, psyche_solana_treasurer::accounts::RunCreateAccounts { payer: *payer, + authority: *main_authority, run, run_collateral, collateral_mint: *collateral_mint, @@ -270,10 +295,11 @@ pub fn treasurer_run_create( psyche_solana_treasurer::instruction::RunCreate { params: psyche_solana_treasurer::logic::RunCreateParams { index: treasurer_index, - main_authority: *main_authority, - join_authority: *join_authority, - run_id: run_id.to_string(), - client_version: client_version.to_string(), + init: psyche_solana_coordinator::logic::InitCoordinatorParams { + run_id: run_id.to_string(), + join_authority: *join_authority, + client_version: client_version.to_string(), + }, }, }, ) @@ -314,31 +340,31 @@ pub fn treasurer_participant_create( payer: *payer, run, participant, - user: *user, system_program: system_program::ID, }, psyche_solana_treasurer::instruction::ParticipantCreate { - params: psyche_solana_treasurer::logic::ParticipantCreateParams {}, + params: psyche_solana_treasurer::logic::ParticipantCreateParams { user: *user }, }, ) } pub fn treasurer_participant_claim( treasurer_index: u64, + claimer: &Pubkey, + claimer_collateral: &Pubkey, collateral_mint: &Pubkey, coordinator_account: &Pubkey, user: &Pubkey, claim_earned_points: u64, ) -> Instruction { - let user_collateral = associated_token::get_associated_token_address(user, collateral_mint); let run = psyche_solana_treasurer::find_run(treasurer_index); let run_collateral = associated_token::get_associated_token_address(&run, collateral_mint); let participant = psyche_solana_treasurer::find_participant(&run, user); anchor_instruction( psyche_solana_treasurer::ID, psyche_solana_treasurer::accounts::ParticipantClaimAccounts { - user: *user, - user_collateral, + claimer: *claimer, + claimer_collateral: *claimer_collateral, run, run_collateral, participant, @@ -347,6 +373,7 @@ pub fn treasurer_participant_claim( }, psyche_solana_treasurer::instruction::ParticipantClaim { params: psyche_solana_treasurer::logic::ParticipantClaimParams { + user: *user, claim_earned_points, }, }, diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/client.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/client.rs index e47ebffb4..49fbe7022 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/client.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/client.rs @@ -19,13 +19,15 @@ use ts_rs::TS; AnchorDeserialize, Serialize, Deserialize, + PartialEq, TS, )] #[repr(C)] #[ts(rename = "SolanaClient")] pub struct Client { pub id: NodeIdentity, - pub _unused: [u8; 8], + #[ts(type = "number[]")] + pub claimer: Pubkey, pub earned: u64, pub slashed: u64, pub active: u64, @@ -35,6 +37,7 @@ impl Debug for Client { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Client") .field("id", &self.id) + .field("claimer", &self.claimer) .field("earned", &self.earned) .field("slashed", &self.slashed) .field("active", &self.active) diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/clients_state.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/clients_state.rs index b4b1da544..b053373d6 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/clients_state.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/clients_state.rs @@ -21,6 +21,7 @@ use crate::program_error::ProgramError; AnchorDeserialize, Serialize, Deserialize, + PartialEq, TS, )] #[repr(C)] @@ -40,6 +41,7 @@ pub struct ClientsState { AnchorDeserialize, Serialize, Deserialize, + PartialEq, TS, )] #[repr(C)] diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/instance_state.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/instance_state.rs index c4010acb7..a438bf55d 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/instance_state.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/instance_state.rs @@ -56,6 +56,7 @@ impl RunMetadata {} Zeroable, AnchorSerialize, AnchorDeserialize, + PartialEq, Serialize, Deserialize, TS, @@ -332,7 +333,11 @@ impl CoordinatorInstanceState { Ok(()) } - pub fn join_run(&mut self, id: NodeIdentity) -> Result<()> { + pub fn join_run( + &mut self, + id: NodeIdentity, + claimer: Pubkey, + ) -> Result<()> { let existing = match self.clients_state.clients.iter_mut().find(|x| x.id == id) { Some(client) => { @@ -357,10 +362,10 @@ impl CoordinatorInstanceState { let new_client = Client { id, + claimer, earned: 0, slashed: 0, active: self.clients_state.next_active, - _unused: Default::default(), }; if self.clients_state.clients.push(new_client).is_err() { diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/lib.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/lib.rs index 9cb58b948..06b9e4fe5 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/lib.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/lib.rs @@ -7,7 +7,10 @@ mod program_error; use anchor_lang::prelude::*; pub use client::Client; +pub use clients_state::ClientsEpochRates; +pub use clients_state::ClientsState; pub use instance_state::CoordinatorInstanceState; +pub use instance_state::RunMetadata; use logic::*; pub use program_error::ProgramError; use psyche_coordinator::Committee; @@ -27,8 +30,6 @@ use serde::Deserialize; use serde::Serialize; use ts_rs::TS; -pub use crate::instance_state::RunMetadata; - declare_id!("4SHugWqSXwKE5fqDchkJcPEqnoZE22VYKtSTVm7axbT7"); pub const SOLANA_MAX_NUM_PENDING_CLIENTS: usize = SOLANA_MAX_NUM_CLIENTS; @@ -125,7 +126,7 @@ pub fn coordinator_account_from_bytes_mut( #[account(zero_copy)] #[repr(C)] -#[derive(Serialize, Deserialize, TS)] +#[derive(Serialize, Deserialize, PartialEq, TS)] pub struct CoordinatorAccount { pub version: u64, pub state: CoordinatorInstanceState, @@ -133,7 +134,7 @@ pub struct CoordinatorAccount { } impl CoordinatorAccount { - pub const VERSION: u64 = 1; + pub const VERSION: u64 = 2; pub fn space_with_discriminator() -> usize { CoordinatorAccount::DISCRIMINATOR.len() @@ -206,11 +207,21 @@ pub mod psyche_solana_coordinator { } account.state.client_version = - FixedString::<96>::try_from(new_version.as_str()).unwrap(); + FixedString::try_from(new_version.as_str()) + .map_err(|_| ProgramError::FixedStringTooLong)?; msg!("new version: {}", account.state.client_version); Ok(()) } + pub fn set_join_authority( + ctx: Context, + join_authority: Pubkey, + ) -> Result<()> { + let account = &mut ctx.accounts.coordinator_instance; + account.join_authority = join_authority; + Ok(()) + } + pub fn set_future_epoch_rates( ctx: Context, epoch_earning_rate_total_shared: Option, @@ -327,6 +338,7 @@ pub struct OwnerCoordinatorAccounts<'info> { pub authority: Signer<'info>, #[account( + mut, seeds = [ CoordinatorInstance::SEEDS_PREFIX, bytes_from_string(&coordinator_instance.run_id) diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/init_coordinator.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/init_coordinator.rs index 5bf9195ae..d8c520a2b 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/init_coordinator.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/init_coordinator.rs @@ -13,6 +13,9 @@ pub struct InitCoordinatorAccounts<'info> { #[account(mut)] pub payer: Signer<'info>, + #[account()] + pub authority: Signer<'info>, + #[account( init, payer = payer, @@ -25,7 +28,7 @@ pub struct InitCoordinatorAccounts<'info> { )] pub coordinator_instance: Box>, - /// CHECK: TODO TODO UNSAFE UNSAFE + /// CHECK: Account will be completely re-written in this instruction and not read #[account( mut, owner = crate::ID, @@ -38,9 +41,8 @@ pub struct InitCoordinatorAccounts<'info> { #[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct InitCoordinatorParams { - pub main_authority: Pubkey, - pub join_authority: Pubkey, pub run_id: String, + pub join_authority: Pubkey, pub client_version: String, } @@ -48,6 +50,9 @@ pub fn init_coordinator_processor( context: Context, params: InitCoordinatorParams, ) -> Result<()> { + if params.run_id.is_empty() { + return err!(ProgramError::RunIdInvalidLength); + } if params.run_id.len() > SOLANA_RUN_ID_MAX_LEN { return err!(ProgramError::RunIdInvalidLength); } @@ -55,17 +60,19 @@ pub fn init_coordinator_processor( // Initialize the coordinator instance let coordinator_instance = &mut context.accounts.coordinator_instance; coordinator_instance.bump = context.bumps.coordinator_instance; - coordinator_instance.main_authority = params.main_authority; + coordinator_instance.main_authority = context.accounts.authority.key(); coordinator_instance.join_authority = params.join_authority; coordinator_instance.coordinator_account = context.accounts.coordinator_account.key(); coordinator_instance.run_id = params.run_id.clone(); + // Initialize the coordinator account let mut data = context.accounts.coordinator_account.try_borrow_mut_data()?; if data.len() != CoordinatorAccount::space_with_discriminator() { return err!(ProgramError::CoordinatorAccountIncorrectSize); } + // Install the correct coordinator account's discriminator, verify that it was zero before init let disc = CoordinatorAccount::DISCRIMINATOR; let data_disc = &mut data[..disc.len()]; @@ -81,12 +88,16 @@ pub fn init_coordinator_processor( account.version = CoordinatorAccount::VERSION; account.nonce = 0; - account.state.client_version = - FixedString::from_str_truncated(¶ms.client_version); - // Setup the run_id const account.state.coordinator.run_id = - FixedString::from_str_truncated(¶ms.run_id); + FixedString::try_from(params.run_id.as_str()) + .map_err(|_| ProgramError::FixedStringTooLong)?; + + // First client version + account.state.client_version = + FixedString::try_from(params.client_version.as_str()) + .map_err(|_| ProgramError::FixedStringTooLong)?; + // Done Ok(()) } diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/join_run.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/join_run.rs index 201a5f6e4..a973eb5e2 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/join_run.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/logic/join_run.rs @@ -44,6 +44,7 @@ pub struct JoinRunAccounts<'info> { #[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct JoinRunParams { pub client_id: NodeIdentity, + pub claimer: Pubkey, } pub fn join_run_processor( @@ -55,5 +56,5 @@ pub fn join_run_processor( } let mut account = context.accounts.coordinator_account.load_mut()?; account.increment_nonce(); - account.state.join_run(params.client_id) + account.state.join_run(params.client_id, params.claimer) } diff --git a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/program_error.rs b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/program_error.rs index 1554e6802..9be541429 100644 --- a/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/program_error.rs +++ b/architectures/decentralized/solana-coordinator/programs/solana-coordinator/src/program_error.rs @@ -74,6 +74,9 @@ pub enum ProgramError { #[msg("run_id must be 32 bytes or less")] RunIdInvalidLength, + + #[msg("Fixed string conversion failed due to string being too long")] + FixedStringTooLong, } impl From for ProgramError { diff --git a/architectures/decentralized/solana-tooling/src/process_coordinator_instructions.rs b/architectures/decentralized/solana-tooling/src/process_coordinator_instructions.rs index 765ad81a8..e28da11fa 100644 --- a/architectures/decentralized/solana-tooling/src/process_coordinator_instructions.rs +++ b/architectures/decentralized/solana-tooling/src/process_coordinator_instructions.rs @@ -33,12 +33,14 @@ use solana_toolbox_endpoint::ToolboxEndpoint; pub async fn process_coordinator_init( endpoint: &mut ToolboxEndpoint, payer: &Keypair, + main_authority: &Keypair, coordinator_account: &Pubkey, params: InitCoordinatorParams, ) -> Result { let coordinator_instance = find_coordinator_instance(¶ms.run_id); let accounts = InitCoordinatorAccounts { payer: payer.pubkey(), + authority: main_authority.pubkey(), coordinator_instance, coordinator_account: *coordinator_account, system_program: system_program::ID, @@ -48,7 +50,9 @@ pub async fn process_coordinator_init( data: InitCoordinator { params }.data(), program_id: psyche_solana_coordinator::ID, }; - endpoint.process_instruction(payer, instruction).await?; + endpoint + .process_instruction_with_signers(payer, instruction, &[main_authority]) + .await?; Ok(coordinator_instance) } @@ -114,6 +118,7 @@ pub async fn process_update( Ok(()) } +#[allow(clippy::too_many_arguments)] pub async fn process_coordinator_join_run( endpoint: &mut ToolboxEndpoint, payer: &Keypair, @@ -122,6 +127,7 @@ pub async fn process_coordinator_join_run( coordinator_instance: &Pubkey, coordinator_account: &Pubkey, client_id: NodeIdentity, + claimer: &Pubkey, ) -> Result<()> { let accounts = JoinRunAccounts { user: user.pubkey(), @@ -132,7 +138,10 @@ pub async fn process_coordinator_join_run( let instruction = Instruction { accounts: accounts.to_account_metas(None), data: JoinRun { - params: JoinRunParams { client_id }, + params: JoinRunParams { + client_id, + claimer: *claimer, + }, } .data(), program_id: psyche_solana_coordinator::ID, @@ -167,7 +176,7 @@ pub async fn process_coordinator_set_paused( Ok(()) } -pub async fn process_coordiantor_set_future_epoch_rates( +pub async fn process_coordinator_set_future_epoch_rates( endpoint: &mut ToolboxEndpoint, payer: &Keypair, authority: &Keypair, diff --git a/architectures/decentralized/solana-tooling/src/process_treasurer_instructions.rs b/architectures/decentralized/solana-tooling/src/process_treasurer_instructions.rs index ed8da52fe..6ecc438d3 100644 --- a/architectures/decentralized/solana-tooling/src/process_treasurer_instructions.rs +++ b/architectures/decentralized/solana-tooling/src/process_treasurer_instructions.rs @@ -28,6 +28,7 @@ use solana_toolbox_endpoint::ToolboxEndpoint; pub async fn process_treasurer_run_create( endpoint: &mut ToolboxEndpoint, payer: &Keypair, + authority: &Keypair, collateral_mint: &Pubkey, coordinator_account: &Pubkey, params: RunCreateParams, @@ -37,9 +38,10 @@ pub async fn process_treasurer_run_create( &run, collateral_mint, ); - let coordinator_instance = find_coordinator_instance(¶ms.run_id); + let coordinator_instance = find_coordinator_instance(¶ms.init.run_id); let accounts = RunCreateAccounts { payer: payer.pubkey(), + authority: authority.pubkey(), collateral_mint: *collateral_mint, run, run_collateral, @@ -55,7 +57,9 @@ pub async fn process_treasurer_run_create( data: RunCreate { params }.data(), program_id: psyche_solana_treasurer::ID, }; - endpoint.process_instruction(payer, instruction).await?; + endpoint + .process_instruction_with_signers(payer, instruction, &[authority]) + .await?; Ok((run, coordinator_instance)) } @@ -89,13 +93,12 @@ pub async fn process_treasurer_run_update( pub async fn process_treasurer_participant_create( endpoint: &mut ToolboxEndpoint, payer: &Keypair, - user: &Keypair, run: &Pubkey, + user: &Pubkey, ) -> Result<()> { - let participant = find_participant(run, &user.pubkey()); + let participant = find_participant(run, user); let accounts = ParticipantCreateAccounts { payer: payer.pubkey(), - user: user.pubkey(), run: *run, participant, system_program: system_program::ID, @@ -103,13 +106,13 @@ pub async fn process_treasurer_participant_create( let instruction = Instruction { accounts: accounts.to_account_metas(None), data: ParticipantCreate { - params: ParticipantCreateParams {}, + params: ParticipantCreateParams { user: *user }, } .data(), program_id: psyche_solana_treasurer::ID, }; endpoint - .process_instruction_with_signers(payer, instruction, &[user]) + .process_instruction_with_signers(payer, instruction, &[]) .await?; Ok(()) } @@ -118,10 +121,11 @@ pub async fn process_treasurer_participant_create( pub async fn process_treasurer_participant_claim( endpoint: &mut ToolboxEndpoint, payer: &Keypair, - user: &Keypair, - user_collateral: &Pubkey, + claimer: &Keypair, + claimer_collateral: &Pubkey, collateral_mint: &Pubkey, run: &Pubkey, + user: &Pubkey, coordinator_account: &Pubkey, claim_earned_points: u64, ) -> Result<()> { @@ -129,10 +133,10 @@ pub async fn process_treasurer_participant_claim( run, collateral_mint, ); - let participant = find_participant(run, &user.pubkey()); + let participant = find_participant(run, user); let accounts = ParticipantClaimAccounts { - user: user.pubkey(), - user_collateral: *user_collateral, + claimer: claimer.pubkey(), + claimer_collateral: *claimer_collateral, run: *run, run_collateral, coordinator_account: *coordinator_account, @@ -143,6 +147,7 @@ pub async fn process_treasurer_participant_claim( accounts: accounts.to_account_metas(None), data: ParticipantClaim { params: ParticipantClaimParams { + user: *user, claim_earned_points, }, } @@ -150,7 +155,7 @@ pub async fn process_treasurer_participant_claim( program_id: psyche_solana_treasurer::ID, }; endpoint - .process_instruction_with_signers(payer, instruction, &[user]) + .process_instruction_with_signers(payer, instruction, &[claimer]) .await?; Ok(()) } diff --git a/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account-v0.so b/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account-v0.so deleted file mode 100644 index 665fd7ef2..000000000 Binary files a/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account-v0.so and /dev/null differ diff --git a/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account-v1.so b/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account-v1.so deleted file mode 100644 index 89ace7a38..000000000 Binary files a/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account-v1.so and /dev/null differ diff --git a/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account.so b/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account.so new file mode 100644 index 000000000..b576d4f55 Binary files /dev/null and b/architectures/decentralized/solana-tooling/tests/fixtures/coordinator-account.so differ diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_data_layout.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_data_layout.rs index 8257cbbd1..90c4ab7d0 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_data_layout.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_data_layout.rs @@ -1,159 +1,204 @@ +use anchor_lang::Discriminator; +use psyche_coordinator::ClientState; +use psyche_coordinator::Coordinator; +use psyche_coordinator::CoordinatorConfig; +use psyche_coordinator::CoordinatorEpochState; +use psyche_coordinator::CoordinatorProgress; use psyche_coordinator::Round; use psyche_coordinator::RunState; +use psyche_coordinator::Witness; +use psyche_coordinator::WitnessProof; use psyche_coordinator::model::Checkpoint; +use psyche_coordinator::model::HttpLLMTrainingDataLocation; use psyche_coordinator::model::HttpTrainingDataLocation; +use psyche_coordinator::model::HubRepo; +use psyche_coordinator::model::LLM; use psyche_coordinator::model::LLMArchitecture; use psyche_coordinator::model::LLMTrainingDataLocation; use psyche_coordinator::model::LLMTrainingDataType; use psyche_coordinator::model::Model; +use psyche_core::Bloom; use psyche_core::CosineLR; use psyche_core::FixedString; use psyche_core::FixedVec; use psyche_core::LearningRateSchedule; +use psyche_core::MerkleRoot; +use psyche_core::NodeIdentity; use psyche_core::OptimizerDefinition; use psyche_core::Shuffle; use psyche_core::SmallBoolean; use psyche_core::TokenSize; +use psyche_solana_coordinator::ClientsEpochRates; +use psyche_solana_coordinator::ClientsState; use psyche_solana_coordinator::CoordinatorAccount; +use psyche_solana_coordinator::CoordinatorInstanceState; +use psyche_solana_coordinator::RunMetadata; use psyche_solana_coordinator::coordinator_account_from_bytes; +use solana_sdk::pubkey::Pubkey; #[tokio::test] pub async fn run() { - let coordinator_bytes = - include_bytes!("../fixtures/coordinator-account-v1.so").to_vec(); - let coordinator_account = - coordinator_account_from_bytes(&coordinator_bytes).unwrap(); - eprintln!("coordinator_account.state:{:#?}", coordinator_account.state); - // Check the general layout for corruption - assert_eq!(coordinator_account.version, CoordinatorAccount::VERSION); - assert_eq!(coordinator_account.nonce, 2); - let state = coordinator_account.state; - assert_eq!(state.is_warmup_first_tick, SmallBoolean::FALSE); - assert_eq!(state.is_training_first_tick, SmallBoolean::FALSE); - assert_eq!(state.client_version, fixed_str("test")); - // Check infos on the coordinator run metadata - let metadata = state.metadata; - assert_eq!(metadata.name, fixed_str("")); - assert_eq!(metadata.description, fixed_str("")); - assert_eq!(metadata.num_parameters, 1100000000); - assert_eq!(metadata.vocab_size, 32768); - // Check on the on the coordinator datastructure - let coordinator = state.coordinator; - assert_eq!(coordinator.run_id, fixed_str("test")); - assert_eq!(coordinator.run_state, RunState::Uninitialized); - assert_eq!(coordinator.run_state_start_unix_timestamp, 0); - assert_eq!(coordinator.pending_pause, SmallBoolean::FALSE); - // Coordinator model - match coordinator.model { - Model::LLM(llm) => { - assert_eq!(llm.max_seq_len, 2048); - assert_eq!(llm.cold_start_warmup_steps, 0); - assert_eq!(llm.architecture, LLMArchitecture::HfLlama); - match llm.checkpoint { - Checkpoint::Hub(hub) => { - assert_eq!( - hub.repo_id, - fixed_str("emozilla/llama2-1.1b-gqa-init") - ); - assert_eq!(hub.revision, None); + let coordinator_account_from_reference = CoordinatorAccount { + version: CoordinatorAccount::VERSION, + state: CoordinatorInstanceState { + metadata: RunMetadata { + name: fixed_str("my-name"), + description: fixed_str("my-description"), + num_parameters: 1100000000, + vocab_size: 4242_32768, + }, + coordinator: Coordinator { + run_id: fixed_str("my-run-id"), + run_state: RunState::RoundTrain, + model: Model::LLM(LLM { + max_seq_len: 2048, + cold_start_warmup_steps: 999, + architecture: LLMArchitecture::HfAuto, + checkpoint: Checkpoint::Hub(HubRepo { + repo_id: fixed_str("my-repo-id"), + revision: Some(fixed_str("my-revision")), + }), + data_type: LLMTrainingDataType::Finetuning, + data_location: LLMTrainingDataLocation::Http( + HttpLLMTrainingDataLocation { + location: HttpTrainingDataLocation::Gcp { + bucket_name: fixed_str("my-bucket-name"), + filter_directory: fixed_str( + "my-filter-directory", + ), + }, + token_size_in_bytes: TokenSize::FourBytes, + shuffle: Shuffle::Seeded([55; 32]), + }, + ), + lr_schedule: LearningRateSchedule::Cosine(CosineLR::new( + 0.0004, 250, 0.666, 25000, 0.00004, + )), + optimizer: OptimizerDefinition::Distro { + clip_grad_norm: Some(1.0), + weight_decay: Some(42.42), + compression_decay: 0.999, + compression_topk: 2, + compression_chunk: 64, + quantize_1bit: true, + }, + }), + config: CoordinatorConfig { + warmup_time: 15, + cooldown_time: 30, + max_round_train_time: 15, + round_witness_time: 1, + global_batch_size_warmup_tokens: 34, + epoch_time: 60, + total_steps: 25000, + init_min_clients: 1, + min_clients: 1, + witness_nodes: 88, + global_batch_size_start: 2048, + global_batch_size_end: 2048, + verification_percent: 42, + waiting_for_members_extra_time: 3, + }, + progress: CoordinatorProgress { + epoch: 8989, + step: 777, + epoch_start_data_index: 574842891, }, - _ => panic!("Expected Hub checkpoint"), - }; - assert_eq!(llm.data_type, LLMTrainingDataType::Pretraining); - match llm.data_location { - LLMTrainingDataLocation::Http(http) => { - match http.location { - HttpTrainingDataLocation::Gcp { - bucket_name, - filter_directory, - } => { - assert_eq!( - bucket_name, - fixed_str("nous-pretraining-public-us") - ); - assert_eq!( - filter_directory, - fixed_str("fineweb-edu-tokenized-llama2") - ); + epoch_state: CoordinatorEpochState { + rounds: [Round { + witnesses: fixed_vec_repeat(Witness { + proof: WitnessProof { + position: 42, + index: 32, + witness: SmallBoolean::TRUE, + }, + participant_bloom: Bloom::new(4, &[7; 8]), + broadcast_bloom: Bloom::new(4, &[6; 8]), + broadcast_merkle: MerkleRoot { inner: [77; 32] }, + }), + data_index: 893322, + random_seed: 871, + height: 1002, + clients_len: 21, + tie_breaker_tasks: 34, + }; 4], + clients: fixed_vec_repeat(psyche_coordinator::Client { + id: NodeIdentity::from_single_key([77; 32]), + state: ClientState::Dropped, + exited_height: 42, + }), + exited_clients: fixed_vec_repeat( + psyche_coordinator::Client { + id: NodeIdentity::from_single_key([99; 32]), + state: ClientState::Dropped, + exited_height: 48, }, - _ => panic!("Expected Gcp data location"), - }; - assert_eq!(http.token_size_in_bytes, TokenSize::TwoBytes); - assert_eq!(http.shuffle, Shuffle::DontShuffle); + ), + rounds_head: 77, + start_step: 88, + last_step: 99, + start_timestamp: 33, + first_round: SmallBoolean::TRUE, + cold_start_epoch: SmallBoolean::TRUE, }, - _ => panic!("Expected Http data location"), - }; - match llm.lr_schedule { - LearningRateSchedule::Cosine(learning_rate) => { - assert_eq!( - learning_rate, - CosineLR::new(0.0004, 250, 0.0, 25000, 0.00004) - ); + pending_pause: SmallBoolean::TRUE, + run_state_start_unix_timestamp: 55_55_555_555, + }, + clients_state: ClientsState { + clients: fixed_vec_repeat(psyche_solana_coordinator::Client { + id: NodeIdentity::from_single_key([33; 32]), + active: 63473857845, + earned: 424242, + slashed: 7878, + claimer: Pubkey::from([88; 32]), + }), + next_active: 63473857845, + current_epoch_rates: ClientsEpochRates { + earning_rate_total_shared: 727272, + slashing_rate_per_client: 7272, }, - _ => panic!("Expected Constant LR schedule"), - }; - match llm.optimizer { - OptimizerDefinition::Distro { - clip_grad_norm, - weight_decay, - compression_decay, - compression_topk, - compression_chunk, - quantize_1bit, - } => { - assert_eq!(clip_grad_norm, Some(1.0)); - assert_eq!(weight_decay, None); - assert_eq!(compression_decay, 0.999); - assert_eq!(compression_topk, 2); - assert_eq!(compression_chunk, 64); - assert_eq!(quantize_1bit, false); + future_epoch_rates: ClientsEpochRates { + earning_rate_total_shared: 424242, + slashing_rate_per_client: 4242, }, - _ => panic!("Expected Distro optimizer"), - } + }, + is_warmup_first_tick: SmallBoolean::TRUE, + is_training_first_tick: SmallBoolean::TRUE, + client_version: fixed_str("my-client-version"), }, + nonce: 78787878, }; - // Coordinator config - assert_eq!(coordinator.config.warmup_time, 15); - assert_eq!(coordinator.config.cooldown_time, 30); - assert_eq!(coordinator.config.max_round_train_time, 15); - assert_eq!(coordinator.config.round_witness_time, 1); - assert_eq!(coordinator.config.global_batch_size_warmup_tokens, 0); - assert_eq!(coordinator.config.epoch_time, 60); - assert_eq!(coordinator.config.total_steps, 25000); - assert_eq!(coordinator.config.init_min_clients, 1); - assert_eq!(coordinator.config.min_clients, 1); - assert_eq!(coordinator.config.witness_nodes, 0); - assert_eq!(coordinator.config.global_batch_size_start, 2048); - assert_eq!(coordinator.config.global_batch_size_end, 2048); - assert_eq!(coordinator.config.verification_percent, 0); - assert_eq!(coordinator.config.waiting_for_members_extra_time, 3); - // Coordinator progress - assert_eq!(coordinator.progress.epoch, 0); - assert_eq!(coordinator.progress.step, 0); - assert_eq!(coordinator.progress.epoch_start_data_index, 0); - // Coordinator epoch state - let epoch_state = coordinator.epoch_state; - assert_eq!(epoch_state.rounds, [Round::default(); 4]); - assert_eq!(epoch_state.clients, FixedVec::default()); - assert_eq!(epoch_state.exited_clients, FixedVec::default()); - assert_eq!(epoch_state.rounds_head, 0); - assert_eq!(epoch_state.start_step, 0); - assert_eq!(epoch_state.last_step, 0); - assert_eq!(epoch_state.start_timestamp, 0); - assert_eq!(epoch_state.first_round, SmallBoolean::FALSE); - assert_eq!(epoch_state.cold_start_epoch, SmallBoolean::FALSE); - // Coordinator clients state - let clients_state = state.clients_state; - assert_eq!(clients_state.clients.len(), 0); - assert_eq!(clients_state.next_active, 0); - let current_epoch_rates = clients_state.current_epoch_rates; - assert_eq!(current_epoch_rates.earning_rate_total_shared, 0); - assert_eq!(current_epoch_rates.slashing_rate_per_client, 0); - let future_epoch_rates = clients_state.future_epoch_rates; - assert_eq!(future_epoch_rates.earning_rate_total_shared, 1000000); - assert_eq!(future_epoch_rates.slashing_rate_per_client, 0); + /* + std::fs::write( + "./tests/fixtures/coordinator-account.so", + bytemuck::bytes_of(&coordinator_account_from_reference), + ) + .unwrap(); + */ + let coordinator_account_snapshot_bytes = &[ + CoordinatorAccount::DISCRIMINATOR, + include_bytes!("../fixtures/coordinator-account.so"), + ] + .concat(); + let coordinator_account_from_snapshot = + coordinator_account_from_bytes(coordinator_account_snapshot_bytes) + .unwrap(); + assert!( + &coordinator_account_from_reference + == coordinator_account_from_snapshot + ); } fn fixed_str(value: &str) -> FixedString { - FixedString::from_str_truncated(value) + FixedString::try_from(value).unwrap() +} + +fn fixed_vec_repeat( + value: T, +) -> FixedVec { + let mut vec = FixedVec::new(); + for _ in 0..N { + vec.push(value).unwrap(); + } + vec } diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_full_round.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_full_round.rs index c078e084c..e2e6cee72 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_full_round.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_full_round.rs @@ -28,6 +28,7 @@ use psyche_solana_tooling::process_coordinator_instructions::process_coordinator use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_tick; use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_witness; use psyche_solana_tooling::process_coordinator_instructions::process_update; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; @@ -45,6 +46,7 @@ pub async fn run() { // Run constants let main_authority = Keypair::new(); let join_authority = Keypair::new(); + let claimer = Pubkey::new_unique(); let client = Keypair::new(); let ticker = Keypair::new(); let warmup_time = 10; @@ -64,10 +66,10 @@ pub async fn run() { let coordinator_instance = process_coordinator_init( &mut endpoint, &payer, + &main_authority, &coordinator_account, InitCoordinatorParams { run_id: "This is a random run id!".to_string(), - main_authority: main_authority.pubkey(), join_authority: join_authority.pubkey(), client_version: "test".to_string(), }, @@ -157,8 +159,7 @@ pub async fn run() { ); // Generate the client key - let client_id = - NodeIdentity::new(client.pubkey().to_bytes(), Default::default()); + let client_id = NodeIdentity::from_single_key(client.pubkey().to_bytes()); // Add client to whitelist let authorization = process_authorizer_authorization_create( @@ -189,6 +190,7 @@ pub async fn run() { &coordinator_instance, &coordinator_account, client_id, + &claimer, ) .await .unwrap_err(); @@ -202,6 +204,7 @@ pub async fn run() { &coordinator_instance, &coordinator_account, client_id, + &claimer, ) .await .unwrap(); @@ -250,6 +253,7 @@ pub async fn run() { &coordinator_instance, &coordinator_account, client_id, + &claimer, ) .await .unwrap(); diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_init_free.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_init_free.rs index 5861837c6..c72db23cb 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_init_free.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_init_free.rs @@ -48,10 +48,10 @@ pub async fn run() { let coordinator_instance = process_coordinator_init( &mut endpoint, &payer, + &main_authority, &coordinator_account, InitCoordinatorParams { run_id: "this is a dummy run_id".to_string(), - main_authority: main_authority.pubkey(), join_authority: join_authority.pubkey(), client_version: "test".to_string(), }, diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_rewards.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_rewards.rs index db33b9304..fb7db761d 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_rewards.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_coordinator_rewards.rs @@ -22,13 +22,14 @@ use psyche_solana_tooling::create_memnet_endpoint::create_memnet_endpoint; use psyche_solana_tooling::get_accounts::get_coordinator_account_state; use psyche_solana_tooling::process_authorizer_instructions::process_authorizer_authorization_create; use psyche_solana_tooling::process_authorizer_instructions::process_authorizer_authorization_grantor_update; -use psyche_solana_tooling::process_coordinator_instructions::process_coordiantor_set_future_epoch_rates; use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_init; use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_join_run; +use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_set_future_epoch_rates; use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_set_paused; use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_tick; use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_witness; use psyche_solana_tooling::process_coordinator_instructions::process_update; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; @@ -46,6 +47,7 @@ pub async fn run() { // Run constants let main_authority = Keypair::new(); let join_authority = Keypair::new(); + let claimer = Pubkey::new_unique(); let mut clients = vec![]; for _ in 0..240 { clients.push(Keypair::new()); @@ -71,10 +73,10 @@ pub async fn run() { let coordinator_instance = process_coordinator_init( &mut endpoint, &payer, + &main_authority, &coordinator_account, InitCoordinatorParams { run_id: "This is a random run id!".to_string(), - main_authority: main_authority.pubkey(), join_authority: join_authority.pubkey(), client_version: "test".to_string(), }, @@ -130,7 +132,7 @@ pub async fn run() { .unwrap(); // Set the reward rate for the epoch - process_coordiantor_set_future_epoch_rates( + process_coordinator_set_future_epoch_rates( &mut endpoint, &payer, &main_authority, @@ -183,7 +185,8 @@ pub async fn run() { &authorization, &coordinator_instance, &coordinator_account, - NodeIdentity::new(client.pubkey().to_bytes(), Default::default()), + NodeIdentity::from_single_key(client.pubkey().to_bytes()), + &claimer, ) .await .unwrap(); diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_claim.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_claim.rs index ebcf43156..82aed0125 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_claim.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_claim.rs @@ -1,9 +1,19 @@ +use psyche_coordinator::CoordinatorConfig; +use psyche_core::NodeIdentity; +use psyche_solana_authorizer::logic::AuthorizationGrantorUpdateParams; use psyche_solana_coordinator::CoordinatorAccount; +use psyche_solana_coordinator::logic::InitCoordinatorParams; +use psyche_solana_coordinator::logic::JOIN_RUN_AUTHORIZATION_SCOPE; use psyche_solana_tooling::create_memnet_endpoint::create_memnet_endpoint; +use psyche_solana_tooling::process_authorizer_instructions::process_authorizer_authorization_create; +use psyche_solana_tooling::process_authorizer_instructions::process_authorizer_authorization_grantor_update; +use psyche_solana_tooling::process_coordinator_instructions::process_coordinator_join_run; use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_participant_claim; use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_participant_create; use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_run_create; +use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_run_update; use psyche_solana_treasurer::logic::RunCreateParams; +use psyche_solana_treasurer::logic::RunUpdateParams; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; @@ -26,6 +36,8 @@ pub async fn run() { let join_authority = Keypair::new(); let client1 = Keypair::new(); let client2 = Keypair::new(); + let claimer1 = Keypair::new(); + let claimer2 = Keypair::new(); // Prepare the collateral mints let collateral1_mint = endpoint @@ -56,32 +68,96 @@ pub async fn run() { .unwrap(); // Create the runs (it should init the underlying coordinators) - let (run1, _) = process_treasurer_run_create( + let (run1, coordinator1_instance) = process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral1_mint, &coordinator1_account, RunCreateParams { index: 41, - run_id: "This is my run's dummy run_id1".to_string(), - main_authority: main_authority.pubkey(), - join_authority: join_authority.pubkey(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: "This is my run's dummy run_id1".to_string(), + join_authority: join_authority.pubkey(), + client_version: "latest".to_string(), + }, }, ) .await .unwrap(); - let (run2, _) = process_treasurer_run_create( + let (run2, coordinator2_instance) = process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral2_mint, &coordinator2_account, RunCreateParams { index: 42, - run_id: "This is my run's dummy run_id2".to_string(), - main_authority: main_authority.pubkey(), - join_authority: join_authority.pubkey(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: "This is my run's dummy run_id2".to_string(), + join_authority: join_authority.pubkey(), + client_version: "latest".to_string(), + }, + }, + ) + .await + .unwrap(); + + // Update the runs' coordinator configs + let dummy_config = CoordinatorConfig { + warmup_time: 10, + cooldown_time: 20, + max_round_train_time: 888, + round_witness_time: 42, + min_clients: 1, + init_min_clients: 1, + global_batch_size_start: 1, + global_batch_size_end: 42, + global_batch_size_warmup_tokens: 0, + verification_percent: 0, + witness_nodes: 0, + epoch_time: 999, + total_steps: 100, + waiting_for_members_extra_time: 3, + }; + process_treasurer_run_update( + &mut endpoint, + &payer, + &main_authority, + &run1, + &coordinator1_instance, + &coordinator1_account, + RunUpdateParams { + join_authority: None, + metadata: None, + config: Some(dummy_config), + model: None, + progress: None, + epoch_earning_rate_total_shared: None, + epoch_slashing_rate_per_client: None, + paused: None, + client_version: None, + }, + ) + .await + .unwrap(); + process_treasurer_run_update( + &mut endpoint, + &payer, + &main_authority, + &run2, + &coordinator2_instance, + &coordinator2_account, + RunUpdateParams { + join_authority: None, + metadata: None, + config: Some(dummy_config), + model: None, + progress: None, + epoch_earning_rate_total_shared: None, + epoch_slashing_rate_per_client: None, + paused: None, + client_version: None, }, ) .await @@ -127,35 +203,35 @@ pub async fn run() { .await .unwrap(); - // Create the clients ATA - let client1_collateral1 = endpoint + // Create the claimers ATA + let claimer1_collateral1 = endpoint .process_spl_associated_token_account_get_or_init( &payer, - &client1.pubkey(), + &claimer1.pubkey(), &collateral1_mint, ) .await .unwrap(); - let client1_collateral2 = endpoint + let claimer1_collateral2 = endpoint .process_spl_associated_token_account_get_or_init( &payer, - &client1.pubkey(), + &claimer1.pubkey(), &collateral2_mint, ) .await .unwrap(); - let client2_collateral1 = endpoint + let claimer2_collateral1 = endpoint .process_spl_associated_token_account_get_or_init( &payer, - &client2.pubkey(), + &claimer2.pubkey(), &collateral1_mint, ) .await .unwrap(); - let client2_collateral2 = endpoint + let claimer2_collateral2 = endpoint .process_spl_associated_token_account_get_or_init( &payer, - &client2.pubkey(), + &claimer2.pubkey(), &collateral2_mint, ) .await @@ -165,44 +241,130 @@ pub async fn run() { process_treasurer_participant_create( &mut endpoint, &payer, - &client1, &run1, + &client1.pubkey(), ) .await .unwrap(); process_treasurer_participant_create( &mut endpoint, &payer, - &client1, &run2, + &client1.pubkey(), ) .await .unwrap(); process_treasurer_participant_create( &mut endpoint, &payer, - &client2, &run1, + &client2.pubkey(), ) .await .unwrap(); process_treasurer_participant_create( &mut endpoint, &payer, - &client2, &run2, + &client2.pubkey(), ) .await .unwrap(); - // Try claiming nothing with proper inputs, it should work but do nothing + // Try claiming before joining, it should fail process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimer1, + &claimer1_collateral1, + &collateral1_mint, + &run1, + &client1.pubkey(), + &coordinator1_account, + 0, + ) + .await + .unwrap_err(); + + // Create and activate the join authorization for everyone + let authorization = process_authorizer_authorization_create( + &mut endpoint, + &payer, + &join_authority, + &Pubkey::default(), + &JOIN_RUN_AUTHORIZATION_SCOPE, + ) + .await + .unwrap(); + process_authorizer_authorization_grantor_update( + &mut endpoint, + &payer, + &join_authority, + &authorization, + AuthorizationGrantorUpdateParams { active: true }, + ) + .await + .unwrap(); + + // Joining the runs + process_coordinator_join_run( &mut endpoint, &payer, &client1, - &client1_collateral1, + &authorization, + &coordinator1_instance, + &coordinator1_account, + NodeIdentity::from_single_key(client1.pubkey().to_bytes()), + &claimer1.pubkey(), + ) + .await + .unwrap(); + process_coordinator_join_run( + &mut endpoint, + &payer, + &client2, + &authorization, + &coordinator1_instance, + &coordinator1_account, + NodeIdentity::from_single_key(client2.pubkey().to_bytes()), + &claimer2.pubkey(), + ) + .await + .unwrap(); + process_coordinator_join_run( + &mut endpoint, + &payer, + &client1, + &authorization, + &coordinator2_instance, + &coordinator2_account, + NodeIdentity::from_single_key(client1.pubkey().to_bytes()), + &claimer1.pubkey(), + ) + .await + .unwrap(); + process_coordinator_join_run( + &mut endpoint, + &payer, + &client2, + &authorization, + &coordinator2_instance, + &coordinator2_account, + NodeIdentity::from_single_key(client2.pubkey().to_bytes()), + &claimer2.pubkey(), + ) + .await + .unwrap(); + + // Try claiming nothing with proper inputs, it should work but do nothing + process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimer1, + &claimer1_collateral1, &collateral1_mint, &run1, + &client1.pubkey(), &coordinator1_account, 0, ) @@ -211,10 +373,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client2, - &client2_collateral1, + &claimer2, + &claimer2_collateral1, &collateral1_mint, &run1, + &client2.pubkey(), &coordinator1_account, 0, ) @@ -223,10 +386,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client1_collateral2, + &claimer1, + &claimer1_collateral2, &collateral2_mint, &run2, + &client1.pubkey(), &coordinator2_account, 0, ) @@ -235,10 +399,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client2, - &client2_collateral2, + &claimer2, + &claimer2_collateral2, &collateral2_mint, &run2, + &client2.pubkey(), &coordinator2_account, 0, ) @@ -249,24 +414,41 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client1_collateral1, + &claimer1, + &claimer1_collateral1, &collateral1_mint, &run1, + &client1.pubkey(), &coordinator1_account, 1, ) .await .unwrap_err(); - // Try claiming using the wrong owner, it should fail + // Try claiming using the wrong client, it should fail process_treasurer_participant_claim( &mut endpoint, &payer, - &client2, - &client1_collateral1, + &claimer1, + &claimer1_collateral1, + &collateral1_mint, + &run1, + &client2.pubkey(), // Wrong client + &coordinator1_account, + 0, + ) + .await + .unwrap_err(); + + // Try claiming using the wrong claimer, it should fail + process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimer2, // Wrong claimer + &claimer1_collateral1, &collateral1_mint, &run1, + &client1.pubkey(), &coordinator1_account, 0, ) @@ -277,10 +459,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client2_collateral1, + &claimer1, + &claimer2_collateral1, // Wrong ATA &collateral1_mint, &run1, + &client1.pubkey(), &coordinator1_account, 0, ) @@ -289,10 +472,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client1_collateral2, + &claimer1, + &claimer1_collateral2, // Wrong ATA &collateral1_mint, &run1, + &client1.pubkey(), &coordinator1_account, 0, ) @@ -303,10 +487,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client1_collateral1, - &collateral2_mint, + &claimer1, + &claimer1_collateral1, + &collateral2_mint, // Wrong mint &run1, + &client1.pubkey(), &coordinator1_account, 0, ) @@ -317,10 +502,11 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client1_collateral1, + &claimer1, + &claimer1_collateral1, &collateral1_mint, - &run2, + &run2, // Wrong run + &client1.pubkey(), &coordinator1_account, 0, ) @@ -331,21 +517,22 @@ pub async fn run() { process_treasurer_participant_claim( &mut endpoint, &payer, - &client1, - &client1_collateral1, + &claimer1, + &claimer1_collateral1, &collateral1_mint, &run1, - &coordinator2_account, + &client1.pubkey(), + &coordinator2_account, // Wrong coordinator account 0, ) .await .unwrap_err(); // Noone should have been able to claim anything yet - assert_amount(&mut endpoint, &client1_collateral1, 0).await; - assert_amount(&mut endpoint, &client2_collateral2, 0).await; - assert_amount(&mut endpoint, &client1_collateral1, 0).await; - assert_amount(&mut endpoint, &client2_collateral2, 0).await; + assert_amount(&mut endpoint, &claimer1_collateral1, 0).await; + assert_amount(&mut endpoint, &claimer2_collateral2, 0).await; + assert_amount(&mut endpoint, &claimer1_collateral1, 0).await; + assert_amount(&mut endpoint, &claimer2_collateral2, 0).await; // All the runs collateral should still be intact assert_amount(&mut endpoint, &run1_collateral1, 1_000_000_000_000).await; diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_update.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_update.rs index 7835ac26d..7a7269af5 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_update.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_create_update.rs @@ -11,6 +11,7 @@ use psyche_core::ConstantLR; use psyche_core::LearningRateSchedule; use psyche_core::OptimizerDefinition; use psyche_solana_coordinator::CoordinatorAccount; +use psyche_solana_coordinator::logic::InitCoordinatorParams; use psyche_solana_tooling::create_memnet_endpoint::create_memnet_endpoint; use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_run_create; use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_run_update; @@ -36,6 +37,7 @@ pub async fn run() { let index = 42; let run_id = "This is my run's dummy run_id".to_string(); let run_update_params = RunUpdateParams { + join_authority: None, metadata: None, config: Some(CoordinatorConfig { warmup_time: 10, @@ -98,14 +100,16 @@ pub async fn run() { process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &Pubkey::new_unique(), RunCreateParams { index, - run_id: run_id.clone(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: run_id.clone(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -115,14 +119,16 @@ pub async fn run() { process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &Pubkey::new_unique(), &coordinator_account, RunCreateParams { index, - run_id: run_id.clone(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: run_id.clone(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -132,14 +138,16 @@ pub async fn run() { let (run, coordinator_instance) = process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &coordinator_account, RunCreateParams { index, - run_id: run_id.clone(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: run_id.clone(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -175,14 +183,16 @@ pub async fn run() { process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &coordinator_account, RunCreateParams { index: index + 1, - run_id: "another run id".to_string(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: "another run id".to_string(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -202,14 +212,16 @@ pub async fn run() { process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &coordinator_account2, RunCreateParams { index, - run_id: "another run id".to_string(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: "another run id".to_string(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -219,14 +231,16 @@ pub async fn run() { process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &coordinator_account2, RunCreateParams { index: index + 1, - run_id: run_id.clone(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: run_id.clone(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -236,14 +250,16 @@ pub async fn run() { process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &coordinator_account2, RunCreateParams { index: index + 1, - run_id: "another run id".to_string(), - main_authority: main_authority.pubkey(), - join_authority: Pubkey::new_unique(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: "another run id".to_string(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await diff --git a/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_full_epoch.rs b/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_full_epoch.rs index 3db8e64ee..538258f96 100644 --- a/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_full_epoch.rs +++ b/architectures/decentralized/solana-tooling/tests/suites/memnet_treasurer_full_epoch.rs @@ -19,6 +19,7 @@ use psyche_solana_authorizer::logic::AuthorizationGranteeUpdateParams; use psyche_solana_authorizer::logic::AuthorizationGrantorUpdateParams; use psyche_solana_coordinator::CoordinatorAccount; use psyche_solana_coordinator::instruction::Witness; +use psyche_solana_coordinator::logic::InitCoordinatorParams; use psyche_solana_coordinator::logic::JOIN_RUN_AUTHORIZATION_SCOPE; use psyche_solana_tooling::create_memnet_endpoint::create_memnet_endpoint; use psyche_solana_tooling::get_accounts::get_coordinator_account_state; @@ -34,6 +35,7 @@ use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_run use psyche_solana_tooling::process_treasurer_instructions::process_treasurer_run_update; use psyche_solana_treasurer::logic::RunCreateParams; use psyche_solana_treasurer::logic::RunUpdateParams; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; @@ -93,14 +95,16 @@ pub async fn run() { let (run, coordinator_instance) = process_treasurer_run_create( &mut endpoint, &payer, + &main_authority, &collateral_mint, &coordinator_account, RunCreateParams { index: 42, - run_id: "This is my run's dummy run_id".to_string(), - main_authority: main_authority.pubkey(), - join_authority: join_authority.pubkey(), - client_version: "latest".to_string(), + init: InitCoordinatorParams { + run_id: "This is my run's dummy run_id".to_string(), + join_authority: Pubkey::new_unique(), + client_version: "latest".to_string(), + }, }, ) .await @@ -148,61 +152,18 @@ pub async fn run() { .await .unwrap(); - // Create the clients ATAs - let mut clients_collateral = vec![]; - for client in &clients { - clients_collateral.push( - endpoint - .process_spl_associated_token_account_get_or_init( - &payer, - &client.pubkey(), - &collateral_mint, - ) - .await - .unwrap(), - ); - } - // Create the participations accounts for client in &clients { process_treasurer_participant_create( &mut endpoint, &payer, - client, &run, + &client.pubkey(), ) .await .unwrap(); } - // Try claiming nothing, it should work, but we earned nothing - process_treasurer_participant_claim( - &mut endpoint, - &payer, - &clients[0], - &clients_collateral[0], - &collateral_mint, - &run, - &coordinator_account, - 0, - ) - .await - .unwrap(); - - // Claiming with the wrong collateral should fail - process_treasurer_participant_claim( - &mut endpoint, - &payer, - &clients[0], - &clients_collateral[1], - &collateral_mint, - &run, - &coordinator_account, - 0, - ) - .await - .unwrap_err(); - // Prepare the coordinator's config process_treasurer_run_update( &mut endpoint, @@ -212,6 +173,7 @@ pub async fn run() { &coordinator_instance, &coordinator_account, RunUpdateParams { + join_authority: Some(join_authority.pubkey()), metadata: None, config: Some(CoordinatorConfig { warmup_time, @@ -294,16 +256,55 @@ pub async fn run() { .await .unwrap(); + // Create the clients's claimers + let mut claimers = vec![]; + for _ in &clients { + claimers.push(Keypair::new()); + } + // The clients can now join the run - for client in &clients { + for i in 0..clients.len() { process_coordinator_join_run( &mut endpoint, &payer, - client, + &clients[i], &authorization, &coordinator_instance, &coordinator_account, - NodeIdentity::new(client.pubkey().to_bytes(), Default::default()), + NodeIdentity::from_single_key(clients[i].pubkey().to_bytes()), + &claimers[i].pubkey(), + ) + .await + .unwrap(); + } + + // Create the clients's claimers's ATA + let mut claimers_collateral = vec![]; + for claimer in &claimers { + claimers_collateral.push( + endpoint + .process_spl_associated_token_account_get_or_init( + &payer, + &claimer.pubkey(), + &collateral_mint, + ) + .await + .unwrap(), + ); + } + + // Try claiming nothing, it should work, but we earned nothing yet + for i in 0..clients.len() { + process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimers[i], + &claimers_collateral[i], + &collateral_mint, + &run, + &clients[i].pubkey(), + &coordinator_account, + 0, ) .await .unwrap(); @@ -400,18 +401,21 @@ pub async fn run() { } // Not yet earned the credit, claiming anything should fail - process_treasurer_participant_claim( - &mut endpoint, - &payer, - &clients[0], - &clients_collateral[0], - &collateral_mint, - &coordinator_instance, - &coordinator_account, - 1, - ) - .await - .unwrap_err(); + for i in 0..clients.len() { + process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimers[i], + &claimers_collateral[i], + &collateral_mint, + &coordinator_instance, + &clients[i].pubkey(), + &coordinator_account, + 1, + ) + .await + .unwrap_err(); + } // Tick from cooldown to new epoch (should increment the earned points) endpoint @@ -429,18 +433,21 @@ pub async fn run() { .unwrap(); // We can claim earned points now, but it should fail because run isnt funded - process_treasurer_participant_claim( - &mut endpoint, - &payer, - &clients[0], - &clients_collateral[0], - &collateral_mint, - &run, - &coordinator_account, - earned_point_per_epoch_per_client, - ) - .await - .unwrap_err(); + for i in 0..clients.len() { + process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimers[i], + &claimers_collateral[i], + &collateral_mint, + &run, + &clients[i].pubkey(), + &coordinator_account, + earned_point_per_epoch_per_client, + ) + .await + .unwrap_err(); + } // We should be able to top-up run treasury at any time endpoint @@ -456,15 +463,14 @@ pub async fn run() { // Now that a new epoch has started, we can claim our earned point for i in 0..clients.len() { - let client = &clients[i]; - let client_collateral = &clients_collateral[i]; process_treasurer_participant_claim( &mut endpoint, &payer, - client, - client_collateral, + &claimers[i], + &claimers_collateral[i], &collateral_mint, &run, + &clients[i].pubkey(), &coordinator_account, earned_point_per_epoch_per_client, ) @@ -473,24 +479,27 @@ pub async fn run() { } // Can't claim anything past the earned points - process_treasurer_participant_claim( - &mut endpoint, - &payer, - &clients[0], - &clients_collateral[0], - &collateral_mint, - &run, - &coordinator_account, - 1, - ) - .await - .unwrap_err(); + for i in 0..clients.len() { + process_treasurer_participant_claim( + &mut endpoint, + &payer, + &claimers[i], + &claimers_collateral[i], + &collateral_mint, + &run, + &clients[i].pubkey(), + &coordinator_account, + 1, + ) + .await + .unwrap_err(); + } // Check that we could claim only exactly the right amount - for client_collateral in &clients_collateral { + for claimer_collateral in &claimers_collateral { assert_eq!( endpoint - .get_spl_token_account(client_collateral) + .get_spl_token_account(claimer_collateral) .await .unwrap() .unwrap() diff --git a/architectures/decentralized/solana-treasurer/audits/Psyche Solana Treasurer - Audit - Edits 2026-03-16.pdf b/architectures/decentralized/solana-treasurer/audits/Psyche Solana Treasurer - Audit - Edits 2026-03-16.pdf new file mode 100644 index 000000000..d73602a14 Binary files /dev/null and b/architectures/decentralized/solana-treasurer/audits/Psyche Solana Treasurer - Audit - Edits 2026-03-16.pdf differ diff --git a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/lib.rs b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/lib.rs index 2888e214c..b6f204d32 100644 --- a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/lib.rs +++ b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/lib.rs @@ -61,9 +61,15 @@ pub mod psyche_solana_treasurer { #[error_code] pub enum ProgramError { - #[msg("Invalid parameter")] - InvalidParameter, - #[msg("run_id must be 32 bytes or less")] RunIdInvalidLength, + + #[msg("Participant's client not found")] + ParticipantClientNotFound, + + #[msg("Claimer signer does not match the expected signer")] + ClaimerSignerMismatch, + + #[msg("Claimed points exceed earned points")] + ClaimedPointsExceedEarnedPoints, } diff --git a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_claim.rs b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_claim.rs index 7afebccf3..d80d5108f 100644 --- a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_claim.rs +++ b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_claim.rs @@ -13,15 +13,15 @@ use crate::state::Run; #[instruction(params: ParticipantClaimParams)] pub struct ParticipantClaimAccounts<'info> { #[account()] - pub user: Signer<'info>, + pub claimer: Signer<'info>, #[account( mut, - constraint = user_collateral.mint == run.collateral_mint, - constraint = user_collateral.owner == user.key(), - constraint = user_collateral.delegate == None.into(), + constraint = claimer_collateral.mint == run.collateral_mint, + constraint = claimer_collateral.owner == claimer.key(), + constraint = claimer_collateral.delegate == None.into(), )] - pub user_collateral: Box>, + pub claimer_collateral: Box>, #[account( mut, @@ -46,7 +46,7 @@ pub struct ParticipantClaimAccounts<'info> { seeds = [ Participant::SEEDS_PREFIX, run.key().as_ref(), - user.key().as_ref() + params.user.as_ref() ], bump = participant.bump )] @@ -58,6 +58,7 @@ pub struct ParticipantClaimAccounts<'info> { #[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct ParticipantClaimParams { + pub user: Pubkey, pub claim_earned_points: u64, } @@ -65,21 +66,23 @@ pub fn participant_claim_processor( context: Context, params: ParticipantClaimParams, ) -> Result<()> { - let mut participant_earned_points = 0; - for client in context - .accounts - .coordinator_account - .load()? + let user_bytes = params.user.as_ref(); + let coordinator_account = context.accounts.coordinator_account.load()?; + let client_state = match coordinator_account .state .clients_state .clients .iter() + .find(|client| client.id.signer() == user_bytes) { - if *client.id.signer() == context.accounts.user.key().to_bytes() { - participant_earned_points = client.earned; - break; - } + Some(info) => info, + None => return err!(ProgramError::ParticipantClientNotFound), + }; + + if context.accounts.claimer.key() != client_state.claimer { + return err!(ProgramError::ClaimerSignerMismatch); } + let participant_earned_points = client_state.earned; let participant = &mut context.accounts.participant; let run = &mut context.accounts.run; @@ -87,7 +90,7 @@ pub fn participant_claim_processor( let participant_unclaimed_earned_points = participant_earned_points - participant.claimed_earned_points; if params.claim_earned_points > participant_unclaimed_earned_points { - return err!(ProgramError::InvalidParameter); + return err!(ProgramError::ClaimedPointsExceedEarnedPoints); } // We distribute 1 collateral per point and let the coordinator decide the point reward rate @@ -106,7 +109,7 @@ pub fn participant_claim_processor( context.accounts.token_program.to_account_info(), Transfer { from: context.accounts.run_collateral.to_account_info(), - to: context.accounts.user_collateral.to_account_info(), + to: context.accounts.claimer_collateral.to_account_info(), authority: context.accounts.run.to_account_info(), }, ) diff --git a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_create.rs b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_create.rs index 2dff39d45..b73f8fbae 100644 --- a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_create.rs +++ b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/participant_create.rs @@ -9,9 +9,6 @@ pub struct ParticipantCreateAccounts<'info> { #[account(mut)] pub payer: Signer<'info>, - #[account()] - pub user: Signer<'info>, - #[account()] pub run: Box>, @@ -22,7 +19,7 @@ pub struct ParticipantCreateAccounts<'info> { seeds = [ Participant::SEEDS_PREFIX, run.key().as_ref(), - user.key().as_ref() + params.user.as_ref() ], bump )] @@ -33,7 +30,9 @@ pub struct ParticipantCreateAccounts<'info> { } #[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct ParticipantCreateParams {} +pub struct ParticipantCreateParams { + pub user: Pubkey, +} pub fn participant_create_processor( context: Context, diff --git a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_create.rs b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_create.rs index 790bd90b7..0280f5432 100644 --- a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_create.rs +++ b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_create.rs @@ -3,13 +3,11 @@ use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::Mint; use anchor_spl::token::Token; use anchor_spl::token::TokenAccount; -use psyche_coordinator::SOLANA_RUN_ID_MAX_LEN; use psyche_solana_coordinator::cpi::accounts::InitCoordinatorAccounts; use psyche_solana_coordinator::cpi::init_coordinator; use psyche_solana_coordinator::logic::InitCoordinatorParams; use psyche_solana_coordinator::program::PsycheSolanaCoordinator; -use crate::ProgramError; use crate::state::Run; #[derive(Accounts)] @@ -18,6 +16,9 @@ pub struct RunCreateAccounts<'info> { #[account(mut)] pub payer: Signer<'info>, + #[account()] + pub authority: Signer<'info>, + #[account( init, payer = payer, @@ -65,26 +66,18 @@ pub struct RunCreateAccounts<'info> { #[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct RunCreateParams { pub index: u64, - pub run_id: String, - pub client_version: String, - pub main_authority: Pubkey, - pub join_authority: Pubkey, + pub init: InitCoordinatorParams, } pub fn run_create_processor( context: Context, params: RunCreateParams, ) -> Result<()> { - if params.run_id.len() > SOLANA_RUN_ID_MAX_LEN { - return err!(ProgramError::RunIdInvalidLength); - } - let run = &mut context.accounts.run; run.bump = context.bumps.run; run.index = params.index; - run.main_authority = params.main_authority; - run.join_authority = params.join_authority; + run.authority = context.accounts.authority.key(); run.coordinator_instance = context.accounts.coordinator_instance.key(); run.coordinator_account = context.accounts.coordinator_account.key(); @@ -101,6 +94,7 @@ pub fn run_create_processor( context.accounts.coordinator_program.to_account_info(), InitCoordinatorAccounts { payer: context.accounts.payer.to_account_info(), + authority: context.accounts.run.to_account_info(), coordinator_instance: context .accounts .coordinator_instance @@ -116,12 +110,7 @@ pub fn run_create_processor( }, ) .with_signer(run_signer_seeds), - InitCoordinatorParams { - main_authority: context.accounts.run.key(), - join_authority: params.join_authority, - run_id: params.run_id.clone(), - client_version: params.client_version.clone(), - }, + params.init, )?; Ok(()) diff --git a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_update.rs b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_update.rs index b1f4e02bc..bd92761e0 100644 --- a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_update.rs +++ b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/logic/run_update.rs @@ -2,11 +2,10 @@ use anchor_lang::prelude::*; use psyche_coordinator::CoordinatorConfig; use psyche_coordinator::CoordinatorProgress; use psyche_coordinator::model::Model; -use psyche_solana_coordinator::CoordinatorAccount; -use psyche_solana_coordinator::CoordinatorInstance; use psyche_solana_coordinator::RunMetadata; use psyche_solana_coordinator::cpi::accounts::OwnerCoordinatorAccounts; use psyche_solana_coordinator::cpi::set_future_epoch_rates; +use psyche_solana_coordinator::cpi::set_join_authority; use psyche_solana_coordinator::cpi::set_paused; use psyche_solana_coordinator::cpi::update; use psyche_solana_coordinator::cpi::update_client_version; @@ -21,17 +20,19 @@ pub struct RunUpdateAccounts<'info> { pub authority: Signer<'info>, #[account( - constraint = run.main_authority == authority.key(), + constraint = run.authority == authority.key(), constraint = run.coordinator_instance == coordinator_instance.key(), constraint = run.coordinator_account == coordinator_account.key(), )] pub run: Box>, - #[account()] - pub coordinator_instance: Account<'info, CoordinatorInstance>, + /// CHECK: This is only used and checked in the CPI to the coordinator program + #[account(mut)] + pub coordinator_instance: UncheckedAccount<'info>, + /// CHECK: This is only used and checked in the CPI to the coordinator program #[account(mut)] - pub coordinator_account: AccountLoader<'info, CoordinatorAccount>, + pub coordinator_account: UncheckedAccount<'info>, #[account()] pub coordinator_program: Program<'info, PsycheSolanaCoordinator>, @@ -46,6 +47,7 @@ pub struct RunUpdateParams { pub epoch_earning_rate_total_shared: Option, pub epoch_slashing_rate_per_client: Option, pub paused: Option, + pub join_authority: Option, pub client_version: Option, } @@ -130,6 +132,27 @@ pub fn run_update_processor( )?; } + if let Some(join_authority) = params.join_authority { + set_join_authority( + CpiContext::new( + context.accounts.coordinator_program.to_account_info(), + OwnerCoordinatorAccounts { + authority: context.accounts.run.to_account_info(), + coordinator_instance: context + .accounts + .coordinator_instance + .to_account_info(), + coordinator_account: context + .accounts + .coordinator_account + .to_account_info(), + }, + ) + .with_signer(run_signer_seeds), + join_authority, + )?; + } + if let Some(client_version) = params.client_version { update_client_version( CpiContext::new( diff --git a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/state/run.rs b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/state/run.rs index f77f1dd54..4147dba59 100644 --- a/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/state/run.rs +++ b/architectures/decentralized/solana-treasurer/programs/solana-treasurer/src/state/run.rs @@ -5,9 +5,7 @@ use anchor_lang::prelude::*; pub struct Run { pub bump: u8, pub index: u64, - - pub main_authority: Pubkey, - pub join_authority: Pubkey, + pub authority: Pubkey, pub coordinator_account: Pubkey, pub coordinator_instance: Pubkey, diff --git a/shared/coordinator/src/coordinator.rs b/shared/coordinator/src/coordinator.rs index 23a213ee1..1ee6904d1 100644 --- a/shared/coordinator/src/coordinator.rs +++ b/shared/coordinator/src/coordinator.rs @@ -168,6 +168,7 @@ pub struct Witness { AnchorDeserialize, Serialize, Deserialize, + PartialEq, TS, Default, Debug, @@ -192,6 +193,7 @@ pub struct WitnessMetadata { AnchorDeserialize, Serialize, Deserialize, + PartialEq, TS, Default, Debug, @@ -237,7 +239,16 @@ pub type HealthChecks = Vec<(NodeIdentity, CommitteeProof)>; pub const NUM_STORED_ROUNDS: usize = 4; #[derive( - Clone, Debug, Zeroable, Copy, Serialize, Deserialize, AnchorDeserialize, AnchorSerialize, TS, + Clone, + Debug, + Zeroable, + Copy, + Serialize, + Deserialize, + AnchorDeserialize, + AnchorSerialize, + PartialEq, + TS, )] #[repr(C)] pub struct CoordinatorConfig { @@ -263,7 +274,16 @@ pub struct CoordinatorConfig { } #[derive( - Clone, Debug, Zeroable, Copy, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize, TS, + Clone, + Debug, + Zeroable, + Copy, + Serialize, + Deserialize, + AnchorSerialize, + AnchorDeserialize, + PartialEq, + TS, )] #[repr(C)] pub struct CoordinatorEpochState { @@ -285,7 +305,16 @@ pub struct CoordinatorEpochState { } #[derive( - Clone, Debug, Zeroable, Copy, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize, TS, + Clone, + Debug, + Zeroable, + Copy, + Serialize, + Deserialize, + AnchorSerialize, + AnchorDeserialize, + PartialEq, + TS, )] #[repr(C)] pub struct CoordinatorProgress { @@ -295,7 +324,16 @@ pub struct CoordinatorProgress { } #[derive( - Clone, Debug, Zeroable, Copy, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize, TS, + Clone, + Debug, + Zeroable, + Copy, + Serialize, + Deserialize, + AnchorSerialize, + AnchorDeserialize, + PartialEq, + TS, )] #[repr(C)] pub struct Coordinator { diff --git a/shared/coordinator/src/model.rs b/shared/coordinator/src/model.rs index 3176f276e..46c0ae111 100644 --- a/shared/coordinator/src/model.rs +++ b/shared/coordinator/src/model.rs @@ -13,7 +13,16 @@ use serde::{Deserialize, Serialize}; use ts_rs::TS; #[derive( - Clone, Debug, Copy, Zeroable, AnchorDeserialize, AnchorSerialize, Serialize, Deserialize, TS, + Clone, + Debug, + Copy, + Zeroable, + AnchorDeserialize, + AnchorSerialize, + Serialize, + Deserialize, + PartialEq, + TS, )] #[repr(C)] pub enum Model { @@ -83,6 +92,7 @@ pub enum LLMTrainingDataType { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -109,6 +119,7 @@ pub enum LLMTrainingDataLocation { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -163,6 +174,7 @@ impl LLMTrainingDataLocationAndWeight { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -184,7 +196,16 @@ pub enum HttpTrainingDataLocation { } #[derive( - AnchorSerialize, AnchorDeserialize, Serialize, Deserialize, Clone, Debug, Zeroable, Copy, TS, + AnchorSerialize, + AnchorDeserialize, + Serialize, + Deserialize, + Clone, + Debug, + Zeroable, + Copy, + PartialEq, + TS, )] #[repr(C)] pub struct LLM { @@ -275,6 +296,7 @@ impl GcsRepo { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] diff --git a/shared/core/src/definitions.rs b/shared/core/src/definitions.rs index 9a5dfd157..d1914ec72 100644 --- a/shared/core/src/definitions.rs +++ b/shared/core/src/definitions.rs @@ -20,6 +20,7 @@ pub trait LearningRateScheduler: Send + Sync { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -69,6 +70,7 @@ impl LearningRateScheduler for ConstantLR { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -195,6 +197,7 @@ impl LearningRateScheduler for CosineLR { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -283,6 +286,7 @@ impl LearningRateScheduler for WarmupStableDecayLR { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] @@ -350,6 +354,7 @@ impl From for LearningRateSchedule { Debug, Zeroable, Copy, + PartialEq, TS, )] #[repr(C)] diff --git a/tools/rust-tools/run-manager/src/commands/run/json_dump_run.rs b/tools/rust-tools/run-manager/src/commands/run/json_dump_run.rs index dfb0fd55b..5e733c15b 100644 --- a/tools/rust-tools/run-manager/src/commands/run/json_dump_run.rs +++ b/tools/rust-tools/run-manager/src/commands/run/json_dump_run.rs @@ -191,8 +191,7 @@ impl Command for CommandJsonDumpRun { Some(json!({ "address": treasurer_run_address.to_string(), "index": treasurer_run_state.index, - "main_authority": treasurer_run_state.main_authority.to_string(), - "join_authority": treasurer_run_state.join_authority.to_string(), + "authority": treasurer_run_state.authority.to_string(), "total_claimed_earned_points": total_claimed_earned_points, "total_claimable_earned_points": total_claimable_earned_points, "total_unclaimed_earned_points": total_unclaimed_earned_points, diff --git a/tools/rust-tools/run-manager/src/commands/run/set_future_epoch_rates.rs b/tools/rust-tools/run-manager/src/commands/run/set_future_epoch_rates.rs index b20637b99..2d3232db9 100644 --- a/tools/rust-tools/run-manager/src/commands/run/set_future_epoch_rates.rs +++ b/tools/rust-tools/run-manager/src/commands/run/set_future_epoch_rates.rs @@ -63,6 +63,7 @@ impl Command for CommandSetFutureEpochRates { &coordinator_account, &main_authority, RunUpdateParams { + join_authority: None, metadata: None, config: None, model: None, diff --git a/tools/rust-tools/run-manager/src/commands/run/set_paused.rs b/tools/rust-tools/run-manager/src/commands/run/set_paused.rs index fe594c1ce..e00c221be 100644 --- a/tools/rust-tools/run-manager/src/commands/run/set_paused.rs +++ b/tools/rust-tools/run-manager/src/commands/run/set_paused.rs @@ -45,6 +45,7 @@ impl Command for CommandSetPaused { &coordinator_account, &main_authority, RunUpdateParams { + join_authority: None, metadata: None, config: None, model: None, diff --git a/tools/rust-tools/run-manager/src/commands/run/update_config.rs b/tools/rust-tools/run-manager/src/commands/run/update_config.rs index 641577307..86dc67c1f 100644 --- a/tools/rust-tools/run-manager/src/commands/run/update_config.rs +++ b/tools/rust-tools/run-manager/src/commands/run/update_config.rs @@ -1,4 +1,5 @@ use crate::commands::Command; +use anchor_lang::prelude::Pubkey; use async_trait::async_trait; use std::path::PathBuf; @@ -40,6 +41,9 @@ pub struct CommandUpdateConfig { // end metadata #[clap(long, env)] pub client_version: Option, + + #[clap(long, env)] + pub join_authority: Option, } #[async_trait] @@ -56,6 +60,7 @@ impl Command for CommandUpdateConfig { num_parameters, vocab_size, client_version, + join_authority, } = self; let main_authority = backend.get_payer(); @@ -160,6 +165,7 @@ impl Command for CommandUpdateConfig { &coordinator_account, &main_authority, RunUpdateParams { + join_authority, metadata, config, model, @@ -194,6 +200,15 @@ impl Command for CommandUpdateConfig { )); } + if let Some(join_authority) = join_authority { + instructions.push(instructions::coordinator_set_join_authority( + &run_id, + &coordinator_account, + &main_authority, + &join_authority, + )); + } + instructions }; let signature = backend diff --git a/tools/rust-tools/run-manager/src/commands/treasury/claim_rewards.rs b/tools/rust-tools/run-manager/src/commands/treasury/claim_rewards.rs index 6f5ff63c5..3b0334c72 100644 --- a/tools/rust-tools/run-manager/src/commands/treasury/claim_rewards.rs +++ b/tools/rust-tools/run-manager/src/commands/treasury/claim_rewards.rs @@ -1,4 +1,5 @@ use crate::commands::Command; +use anchor_lang::prelude::Pubkey; use anchor_spl::{associated_token, token}; use anyhow::{Context, Result}; use async_trait::async_trait; @@ -14,6 +15,8 @@ pub struct CommandTreasurerClaimRewards { pub run_id: String, #[clap(long, env)] pub treasurer_index: Option, + #[clap(long, env)] + pub user: Option, } #[async_trait] @@ -22,6 +25,7 @@ impl Command for CommandTreasurerClaimRewards { let Self { run_id, treasurer_index, + user, } = self; let treasurer_index = backend @@ -55,35 +59,40 @@ impl Command for CommandTreasurerClaimRewards { native_amount_to_ui_amount(treasurer_run_collateral_amount, collateral_mint_decimals) ); - let user = backend.get_payer(); - println!("User: {user}"); + let claimer = backend.get_payer(); + println!("Claimer: {claimer}"); - let user_collateral_address = associated_token::get_associated_token_address( - &user, + let claimer_collateral_address = associated_token::get_associated_token_address( + &claimer, &treasurer_run_state.collateral_mint, ); - if backend.get_balance(&user_collateral_address).await? == 0 { + if backend.get_balance(&claimer_collateral_address).await? == 0 { let instruction = associated_token::spl_associated_token_account::instruction::create_associated_token_account_idempotent( &backend.get_payer(), - &user, + &claimer, &treasurer_run_state.collateral_mint, &token::ID, ); let signature = backend - .send_and_retry("Create user ATA", &[instruction], &[]) + .send_and_retry("Create claimer ATA", &[instruction], &[]) .await?; - println!("Created associated token account for user during transaction: {signature}"); + println!( + "Created associated token account for claimer during transaction: {signature}" + ); } - let user_collateral_amount = backend - .get_token_account(&user_collateral_address) + let claimer_collateral_amount = backend + .get_token_account(&claimer_collateral_address) .await? .amount; println!( - "User collateral amount: {}", - native_amount_to_ui_amount(user_collateral_amount, collateral_mint_decimals) + "Claimer collateral amount: {}", + native_amount_to_ui_amount(claimer_collateral_amount, collateral_mint_decimals) ); + let user = user.unwrap_or(backend.get_payer()); + println!("User: {user}"); + let treasurer_participant_address = psyche_solana_treasurer::find_participant(&treasurer_run_address, &user); if backend.get_balance(&treasurer_participant_address).await? == 0 { @@ -143,6 +152,8 @@ impl Command for CommandTreasurerClaimRewards { let instruction = instructions::treasurer_participant_claim( treasurer_index, + &claimer, + &claimer_collateral_address, &treasurer_run_state.collateral_mint, &treasurer_run_state.coordinator_account, &user, diff --git a/website/backend/src/coordinatorChainLoop.ts b/website/backend/src/coordinatorChainLoop.ts index 21aa04dc2..c519425ae 100644 --- a/website/backend/src/coordinatorChainLoop.ts +++ b/website/backend/src/coordinatorChainLoop.ts @@ -199,8 +199,8 @@ export async function startWatchCoordinatorChainLoop( // so it's safe to hardcode the index here. switch (decoded.name) { case 'init_coordinator': { - const runPdaAddr = i.accounts[1].toString() - const coordinatorAddr = i.accounts[2].toString() + const runPdaAddr = i.accounts[2].toString() + const coordinatorAddr = i.accounts[3].toString() const expectedRunAddr = getRunPDA( coordinator.programId, decoded.data.params.run_id @@ -342,6 +342,17 @@ export async function startWatchCoordinatorChainLoop( }) break } + case 'set_join_authority': { + const runPdaAddr = i.accounts[1].toString() + const coordinatorAddr = i.accounts[2].toString() + runUpdates.getAndTouchCurrentRun({ + runPdaAddr, + coordinatorAddr, + decoded, + tx, + }) + break + } case 'update_client_version': { const runPdaAddr = i.accounts[1].toString() const coordinatorAddr = i.accounts[2].toString()