Skip to content

Commit b62b086

Browse files
Merge branch 'main' into vbrunet/2025_11_30-airdrop-contract
2 parents 3398b81 + 730858d commit b62b086

File tree

65 files changed

+4114
-1090
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+4114
-1090
lines changed

.github/workflows/solana-build-anchor-programs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
uses: actions/cache@v4
1717
with:
1818
path: validator-image.tar.gz
19-
key: validator-image-${{ runner.os }}-${{ hashFiles('architectures/decentralized/solana-coordinator/**/*.rs', 'architectures/decentralized/solana-coordinator/**/*.toml', 'architectures/decentralized/solana-coordinator/Cargo.lock', 'architectures/decentralized/solana-authorizer/**/*.rs', 'architectures/decentralized/solana-authorizer/**/*.toml', 'architectures/decentralized/solana-authorizer/Cargo.lock', 'docker/test/psyche_solana_validator_entrypoint.sh', 'nix/docker.nix', 'flake.lock') }}
19+
key: validator-image-${{ runner.os }}-${{ hashFiles('shared/coordinator/src/coordinator.rs', 'architectures/decentralized/solana-coordinator/**/*.rs', 'architectures/decentralized/solana-coordinator/**/*.toml', 'architectures/decentralized/solana-coordinator/Cargo.lock', 'architectures/decentralized/solana-authorizer/**/*.rs', 'architectures/decentralized/solana-authorizer/**/*.toml', 'architectures/decentralized/solana-authorizer/Cargo.lock', 'docker/test/psyche_solana_validator_entrypoint.sh', 'nix/docker.nix', 'flake.lock') }}
2020
lookup-only: true
2121

2222
# Build validator image if cache fails
@@ -86,7 +86,7 @@ jobs:
8686
uses: actions/cache/save@v4
8787
with:
8888
path: validator-image.tar.gz
89-
key: validator-image-${{ runner.os }}-${{ hashFiles('architectures/decentralized/solana-coordinator/**/*.rs', 'architectures/decentralized/solana-coordinator/**/*.toml', 'architectures/decentralized/solana-coordinator/Cargo.lock', 'architectures/decentralized/solana-authorizer/**/*.rs', 'architectures/decentralized/solana-authorizer/**/*.toml', 'architectures/decentralized/solana-authorizer/Cargo.lock', 'docker/test/psyche_solana_validator_entrypoint.sh', 'nix/docker.nix', 'flake.lock') }}
89+
key: validator-image-${{ runner.os }}-${{ hashFiles('shared/coordinator/src/coordinator.rs', 'architectures/decentralized/solana-coordinator/**/*.rs', 'architectures/decentralized/solana-coordinator/**/*.toml', 'architectures/decentralized/solana-coordinator/Cargo.lock', 'architectures/decentralized/solana-authorizer/**/*.rs', 'architectures/decentralized/solana-authorizer/**/*.toml', 'architectures/decentralized/solana-authorizer/Cargo.lock', 'docker/test/psyche_solana_validator_entrypoint.sh', 'nix/docker.nix', 'flake.lock') }}
9090

9191
- name: Build complete
9292
if: steps.cache-validator.outputs.cache-hit != 'true'

.github/workflows/solana-integration-test-base.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
uses: actions/cache/restore@v4
5151
with:
5252
path: validator-image.tar.gz
53-
key: validator-image-${{ runner.os }}-${{ hashFiles('architectures/decentralized/solana-coordinator/**/*.rs', 'architectures/decentralized/solana-coordinator/**/*.toml', 'architectures/decentralized/solana-coordinator/Cargo.lock', 'architectures/decentralized/solana-authorizer/**/*.rs', 'architectures/decentralized/solana-authorizer/**/*.toml', 'architectures/decentralized/solana-authorizer/Cargo.lock', 'docker/test/psyche_solana_validator_entrypoint.sh', 'nix/docker.nix', 'flake.lock') }}
53+
key: validator-image-${{ runner.os }}-${{ hashFiles('shared/coordinator/src/coordinator.rs', 'architectures/decentralized/solana-coordinator/**/*.rs', 'architectures/decentralized/solana-coordinator/**/*.toml', 'architectures/decentralized/solana-coordinator/Cargo.lock', 'architectures/decentralized/solana-authorizer/**/*.rs', 'architectures/decentralized/solana-authorizer/**/*.toml', 'architectures/decentralized/solana-authorizer/Cargo.lock', 'docker/test/psyche_solana_validator_entrypoint.sh', 'nix/docker.nix', 'flake.lock') }}
5454
fail-on-cache-miss: true
5555

5656
- name: Load Validator Image

architectures/centralized/server/src/app.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,22 +177,11 @@ impl App {
177177

178178
let training_data_server = match &coordinator.model {
179179
Model::LLM(LLM {
180-
data_locations,
180+
data_location,
181181
checkpoint,
182182
..
183183
}) => {
184-
let data_location_server_urls:Vec<_> = data_locations.iter().filter_map(|l| match l {LLMTrainingDataLocation::Server(url) => Some(url.to_string()), _=> None}).collect();
185-
186-
if data_location_server_urls.is_empty() {
187-
None
188-
} else {
189-
if data_location_server_urls.len() > 1 {
190-
bail!("More than one LLMTrainingDataLocation::Server configured, but we only support hosting a single one.");
191-
}
192-
193-
// we know there's a single url, and it's the one that includes the port we want to host on.
194-
let url = data_location_server_urls.first().unwrap();
195-
184+
if let LLMTrainingDataLocation::Server(url) = data_location {
196185
match checkpoint {
197186
Checkpoint::Hub(hub_repo) => {
198187
let repo_id = String::from(&hub_repo.repo_id);
@@ -217,7 +206,7 @@ impl App {
217206
}
218207
}
219208

220-
let server_addr: SocketAddr = url.parse().map_err(|e| {
209+
let server_addr: SocketAddr = String::from(url).parse().map_err(|e| {
221210
anyhow!("Failed to parse training data server URL {:?}: {}", url, e)
222211
})?;
223212
let data_server_port = server_addr.port();
@@ -242,6 +231,8 @@ impl App {
242231
DataProviderTcpServer::start(local_data_provider, backend, data_server_port)
243232
.await?;
244233
Some((tx, data_server))
234+
} else {
235+
None
245236
}
246237
}
247238
};

architectures/decentralized/solana-client/src/app.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,12 @@ use tokio::{
3232
time::{Interval, MissedTickBehavior, interval},
3333
};
3434
use tokio_util::sync::CancellationToken;
35-
use tracing::{debug, info};
35+
use tracing::{debug, info, warn};
3636

3737
pub(super) type Tabs = TabbedWidget<(ClientTUI, CoordinatorTui, NetworkTui, LoggerWidget)>;
3838
pub const TAB_NAMES: [&str; 4] = ["Client", "Coordinator", "Network", "Logger"];
3939
type TabsData = <Tabs as CustomWidget>::Data;
4040

41-
const CLIENT_VERSION: &str = "latest";
42-
4341
pub struct App {
4442
run_id: String,
4543
cluster: Cluster,
@@ -182,22 +180,28 @@ impl App {
182180
);
183181

184182
// Check client version compatibility before joining
185-
let client_version = CLIENT_VERSION.to_string();
186-
info!("Psyche Client version: {}", client_version);
187-
188-
if client_version != coordinator_client_version && coordinator_client_version != "test" {
189-
tracing::error!(
183+
let client_version = std::env::var("CLIENT_VERSION").ok();
184+
if let Some(client_version) = client_version {
185+
info!("Psyche Client version: {}", client_version);
186+
if client_version != coordinator_client_version && coordinator_client_version != "test"
187+
{
188+
tracing::error!(
189+
client_version = %client_version,
190+
coordinator_client_version = %coordinator_client_version,
191+
"Version mismatch detected. Client version does not match coordinator version."
192+
);
193+
std::process::exit(10);
194+
}
195+
info!(
190196
client_version = %client_version,
191197
coordinator_client_version = %coordinator_client_version,
192-
"Version mismatch detected. Client version does not match coordinator version."
198+
"Version check passed"
193199
);
194-
std::process::exit(10);
200+
} else {
201+
warn!(
202+
"Client version env variable was not set - continuing without validating with Coordinator client version"
203+
)
195204
}
196-
info!(
197-
client_version = %client_version,
198-
coordinator_client_version = %coordinator_client_version,
199-
"Version check passed"
200-
);
201205

202206
let backend_runner = backend
203207
.start(self.run_id.clone(), coordinator_account)

architectures/decentralized/solana-client/src/backend.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use anchor_client::{
1717
signature::{Keypair, Signature, Signer},
1818
},
1919
};
20-
use anchor_spl::token;
2120
use anyhow::{Context, Result, anyhow};
2221
use futures_util::StreamExt;
2322
use psyche_client::IntegrationTestLogMarker;
@@ -400,11 +399,22 @@ impl SolanaBackend {
400399
.map_err(|error| anyhow!("Unable to decode treasurer participant data: {error}"))
401400
}
402401

403-
pub async fn get_token_amount(&self, token_account: &Pubkey) -> Result<u64> {
402+
pub async fn get_token_account(
403+
&self,
404+
token_account: &Pubkey,
405+
) -> Result<anchor_spl::token::spl_token::state::Account> {
404406
let data = self.get_data(token_account).await?;
405-
Ok(token::spl_token::state::Account::unpack(&data)
406-
.map_err(|error| anyhow!("Unable to decode token account data: {error}"))?
407-
.amount)
407+
anchor_spl::token::spl_token::state::Account::unpack(&data)
408+
.map_err(|error| anyhow!("Unable to decode token account data: {error}"))
409+
}
410+
411+
pub async fn get_token_mint(
412+
&self,
413+
mint: &Pubkey,
414+
) -> Result<anchor_spl::token::spl_token::state::Mint> {
415+
let data = self.get_data(mint).await?;
416+
anchor_spl::token::spl_token::state::Mint::unpack(&data)
417+
.map_err(|error| anyhow!("Unable to decode mint account data: {error}"))
408418
}
409419

410420
pub fn compute_deterministic_treasurer_index(
@@ -530,7 +540,8 @@ impl SolanaBackend {
530540
signers: &[Arc<Keypair>],
531541
) -> Result<Signature> {
532542
// TODO (vbrunet) - can we improve the retry mechanism here
533-
for _ in 0..SEND_RETRIES {
543+
let mut retries = 0;
544+
loop {
534545
for program_coordinator in program_coordinators {
535546
let mut request = program_coordinator.request();
536547
for instruction in instructions {
@@ -546,14 +557,17 @@ impl SolanaBackend {
546557
return Ok(signature);
547558
}
548559
Err(error) => {
560+
retries += 1;
561+
if retries >= SEND_RETRIES {
562+
return Err(anyhow!(
563+
"Could not send transaction: {name}, after {retries} retries: {error}"
564+
));
565+
}
549566
warn!("Error sending transaction: {name}: {error}, retrying");
550567
}
551568
}
552569
}
553570
}
554-
Err(anyhow!(
555-
"Could not send transaction: {name}, all attempts failed"
556-
))
557571
}
558572
}
559573

architectures/decentralized/solana-client/src/command/json_dump_run.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde_json::json;
66
use serde_json::to_string_pretty;
77

88
use crate::SolanaBackend;
9+
use crate::utils::native_amount_to_ui_amount;
910

1011
#[derive(Debug, Clone, Args)]
1112
#[command()]
@@ -103,6 +104,7 @@ pub async fn command_json_dump_run_execute(
103104
"address": coordinator_account_address.to_string(),
104105
"run_id": coordinator_account_state.state.coordinator.run_id,
105106
"setup": {
107+
"client_version": coordinator_account_state.state.client_version,
106108
"metadata": coordinator_account_state.state.metadata,
107109
"model": coordinator_account_state.state.coordinator.model,
108110
"config": coordinator_account_state.state.coordinator.config,
@@ -149,49 +151,64 @@ pub async fn command_json_dump_run_execute(
149151
&treasurer_run_state.collateral_mint,
150152
);
151153
let treasurer_run_collateral_amount = backend
152-
.get_token_amount(&treasurer_run_collateral_address)
153-
.await?;
154+
.get_token_account(&treasurer_run_collateral_address)
155+
.await?
156+
.amount;
157+
158+
let collateral_mint_decimals = backend
159+
.get_token_mint(&treasurer_run_state.collateral_mint)
160+
.await?
161+
.decimals;
154162

155163
let total_claimed_earned_points = treasurer_run_state.total_claimed_earned_points;
156164
let total_claimable_earned_points = coordinator_account_clients_sum_earned;
157165
let total_unclaimed_earned_points =
158166
total_claimable_earned_points.saturating_sub(total_claimed_earned_points);
159167

160-
let total_funded_unearned_points =
161-
i128::from(treasurer_run_collateral_amount) - i128::from(total_unclaimed_earned_points);
162-
163-
let estimated_earned_points_per_epoch = u64::try_from(
164-
coordinator_account_state
165-
.state
166-
.coordinator
167-
.epoch_state
168-
.clients
169-
.len(),
170-
)
171-
.unwrap()
172-
* coordinator_account_state
173-
.state
174-
.clients_state
175-
.current_epoch_rates
176-
.earning_rate_total_shared;
177-
let estimated_funded_epochs_count = if estimated_earned_points_per_epoch == 0 {
168+
// 1:1 mapping between earned points and collateral amount
169+
let total_unclaimed_collateral_amount = total_unclaimed_earned_points;
170+
let burn_collateral_amount_per_epoch = coordinator_account_state
171+
.state
172+
.clients_state
173+
.current_epoch_rates
174+
.earning_rate_total_shared;
175+
176+
let total_missing_collateral_amount =
177+
total_unclaimed_collateral_amount.saturating_sub(treasurer_run_collateral_amount);
178+
let total_surplus_collateral_amount =
179+
treasurer_run_collateral_amount.saturating_sub(total_unclaimed_collateral_amount);
180+
181+
let estimated_funded_epochs_count = if burn_collateral_amount_per_epoch == 0 {
178182
json!(f64::INFINITY)
179183
} else {
180-
json!(total_funded_unearned_points / i128::from(estimated_earned_points_per_epoch))
184+
json!(total_surplus_collateral_amount as f64 / burn_collateral_amount_per_epoch as f64)
181185
};
182186

183187
Some(json!({
184188
"address": treasurer_run_address.to_string(),
185189
"index": treasurer_run_state.index,
186190
"main_authority": treasurer_run_state.main_authority.to_string(),
187191
"join_authority": treasurer_run_state.join_authority.to_string(),
188-
"collateral_mint": treasurer_run_state.collateral_mint.to_string(),
189-
"funded_collateral_amount": treasurer_run_collateral_amount,
190192
"total_claimed_earned_points": total_claimed_earned_points,
191193
"total_claimable_earned_points": total_claimable_earned_points,
192194
"total_unclaimed_earned_points": total_unclaimed_earned_points,
193-
"total_funded_unearned_points": total_funded_unearned_points,
194-
"estimated_earned_points_per_epoch": estimated_earned_points_per_epoch,
195+
"collateral_mint": treasurer_run_state.collateral_mint.to_string(),
196+
"funded_collateral_amount": native_amount_to_ui_amount(
197+
treasurer_run_collateral_amount,
198+
collateral_mint_decimals
199+
),
200+
"total_missing_collateral_amount": native_amount_to_ui_amount(
201+
total_missing_collateral_amount,
202+
collateral_mint_decimals
203+
),
204+
"total_surplus_collateral_amount": native_amount_to_ui_amount(
205+
total_surplus_collateral_amount,
206+
collateral_mint_decimals
207+
),
208+
"burn_collateral_amount_per_epoch": native_amount_to_ui_amount(
209+
burn_collateral_amount_per_epoch,
210+
collateral_mint_decimals
211+
),
195212
"estimated_funded_epochs_count": estimated_funded_epochs_count,
196213
}))
197214
} else {

architectures/decentralized/solana-client/src/command/json_dump_user.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde_json::json;
66
use serde_json::to_string_pretty;
77

88
use crate::SolanaBackend;
9+
use crate::utils::native_amount_to_ui_amount;
910

1011
#[derive(Debug, Clone, Args)]
1112
#[command()]
@@ -92,14 +93,20 @@ pub async fn command_json_dump_user_execute(
9293
let treasurer_run_address = psyche_solana_treasurer::find_run(treasurer_index);
9394
let treasurer_run_state = backend.get_treasurer_run(&treasurer_run_address).await?;
9495

96+
let collateral_mint_decimals = backend
97+
.get_token_mint(&treasurer_run_state.collateral_mint)
98+
.await?
99+
.decimals;
100+
95101
let user_collateral_address = associated_token::get_associated_token_address(
96102
&address,
97103
&treasurer_run_state.collateral_mint,
98104
);
99105
let user_collateral_amount = backend
100-
.get_token_amount(&user_collateral_address)
106+
.get_token_account(&user_collateral_address)
101107
.await
102-
.ok();
108+
.map(|account| account.amount)
109+
.unwrap_or(0);
103110

104111
let treasurer_participant_address =
105112
psyche_solana_treasurer::find_participant(&treasurer_run_address, &address);
@@ -110,9 +117,19 @@ pub async fn command_json_dump_user_execute(
110117

111118
Some(json!({
112119
"collateral_mint": treasurer_run_state.collateral_mint.to_string(),
113-
"collateral_amount": user_collateral_amount,
114-
"claimed_earned_points": treasurer_participant_state.as_ref().map(|state| state.claimed_earned_points),
115-
"claimed_collateral_amount": treasurer_participant_state.as_ref().map(|state| state.claimed_collateral_amount),
120+
"collateral_amount": native_amount_to_ui_amount(
121+
user_collateral_amount,
122+
collateral_mint_decimals
123+
),
124+
"claimed_earned_points": treasurer_participant_state
125+
.as_ref()
126+
.map(|state| state.claimed_earned_points),
127+
"claimed_collateral_amount": treasurer_participant_state
128+
.as_ref()
129+
.map(|state| native_amount_to_ui_amount(
130+
state.claimed_collateral_amount,
131+
collateral_mint_decimals
132+
)),
116133
}))
117134
} else {
118135
None

0 commit comments

Comments
 (0)