Skip to content

Commit f0ae855

Browse files
authored
Merge pull request #104 from Ellipsis-Labs/verify-with-signer
Verify with signer
2 parents eaf1f69 + c6b0406 commit f0ae855

File tree

5 files changed

+210
-5
lines changed

5 files changed

+210
-5
lines changed

src/api/client.rs

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
use anyhow::anyhow;
22
use crossbeam_channel::{unbounded, Receiver};
33
use indicatif::{HumanDuration, ProgressBar, ProgressStyle};
4-
use reqwest::Client;
4+
use reqwest::{Client, Response};
55
use serde_json::json;
6+
use solana_client::rpc_client::RpcClient;
67
use solana_sdk::pubkey::Pubkey;
78
use std::thread;
89
use std::time::{Duration, Instant};
910

1011
use crate::api::models::{
11-
ErrorResponse, JobResponse, JobStatus, JobVerificationResponse, VerifyResponse,
12+
ErrorResponse, JobResponse, JobStatus, JobVerificationResponse, RemoteStatusResponseWrapper,
13+
VerifyResponse,
1214
};
15+
use crate::solana_program::get_program_pda;
16+
use crate::{get_genesis_hash, MAINNET_GENESIS_HASH};
1317

1418
// URL for the remote server
1519
pub const REMOTE_SERVER_URL: &str = "https://verify.osec.io";
@@ -108,10 +112,56 @@ pub async fn send_job_to_remote(
108112
.send()
109113
.await?;
110114

115+
handle_submission_response(&client, response, program_id).await
116+
}
117+
118+
pub async fn send_job_with_uploader_to_remote(
119+
connection: &RpcClient,
120+
program_id: &Pubkey,
121+
uploader: &Pubkey,
122+
) -> anyhow::Result<()> {
123+
// Check that PDA exists before sending job
124+
let genesis_hash = get_genesis_hash(connection)?;
125+
if genesis_hash != MAINNET_GENESIS_HASH {
126+
return Err(anyhow!("Remote verification only works with mainnet. Please omit the --remote flag to verify locally."));
127+
}
128+
get_program_pda(connection, program_id, Some(uploader.to_string())).await?;
129+
130+
let client = Client::builder()
131+
.timeout(Duration::from_secs(18000))
132+
.build()?;
133+
134+
// Send the POST request
135+
let response = client
136+
.post(format!("{}/verify-with-signer", REMOTE_SERVER_URL))
137+
.json(&json!({
138+
"program_id": program_id.to_string(),
139+
"signer": uploader.to_string(),
140+
"repository": "",
141+
"commit_hash": "",
142+
}))
143+
.send()
144+
.await?;
145+
146+
handle_submission_response(&client, response, program_id).await
147+
}
148+
149+
pub async fn handle_submission_response(
150+
client: &Client,
151+
response: Response,
152+
program_id: &Pubkey,
153+
) -> anyhow::Result<()> {
111154
if response.status().is_success() {
112-
let status_response: VerifyResponse = response.json().await?;
155+
// First get the raw text to preserve it in case of parsing failure
156+
let response_text = response.text().await?;
157+
let status_response =
158+
serde_json::from_str::<VerifyResponse>(&response_text).map_err(|e| {
159+
eprintln!("Failed to parse response as VerifyResponse: {}", e);
160+
eprintln!("Raw response: {}", response_text);
161+
anyhow!("Failed to parse server response")
162+
})?;
113163
let request_id = status_response.request_id;
114-
println!("Verification request sent. ✅");
164+
println!("Verification request sent with request id: {}", request_id);
115165
println!("Verification in progress... ⏳");
116166
// Span new thread for polling the server for status
117167
// Create a channel for communication between threads
@@ -228,3 +278,32 @@ async fn check_job_status(client: &Client, request_id: &str) -> anyhow::Result<J
228278
))?
229279
}
230280
}
281+
282+
pub async fn get_remote_status(program_id: Pubkey) -> anyhow::Result<()> {
283+
let client = Client::builder()
284+
.timeout(Duration::from_secs(18000))
285+
.build()?;
286+
287+
let response = client
288+
.get(format!(
289+
"{}/status-all/{}",
290+
REMOTE_SERVER_URL,
291+
program_id.to_string()
292+
))
293+
.send()
294+
.await?;
295+
296+
let status: RemoteStatusResponseWrapper = response.json().await?;
297+
println!("{}", status);
298+
Ok(())
299+
}
300+
301+
pub async fn get_remote_job(job_id: &str) -> anyhow::Result<()> {
302+
let client = Client::builder()
303+
.timeout(Duration::from_secs(18000))
304+
.build()?;
305+
306+
let job = check_job_status(&client, job_id).await?;
307+
println!("{}", job);
308+
Ok(())
309+
}

src/api/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@ mod client;
22
mod models;
33
mod solana;
44

5+
pub use client::get_remote_job;
6+
pub use client::get_remote_status;
57
pub use client::send_job_to_remote;
8+
pub use client::send_job_with_uploader_to_remote;
69
pub use solana::get_last_deployed_slot;

src/api/models.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ pub struct JobResponse {
3434
pub respose: Option<JobVerificationResponse>,
3535
}
3636

37+
impl std::fmt::Display for JobResponse {
38+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39+
if let Some(response) = &self.respose {
40+
writeln!(f, "{}", response)?;
41+
} else {
42+
writeln!(f, "Status: {:?}", self.status)?;
43+
}
44+
Ok(())
45+
}
46+
}
47+
3748
#[derive(Debug, Serialize, Deserialize)]
3849
pub enum JobStatus {
3950
#[serde(rename = "in_progress")]
@@ -54,3 +65,59 @@ pub struct JobVerificationResponse {
5465
pub executable_hash: String,
5566
pub repo_url: String,
5667
}
68+
69+
impl std::fmt::Display for JobVerificationResponse {
70+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71+
writeln!(f, "Status: {:?}", self.status)?;
72+
writeln!(f, "Message: {}", self.message)?;
73+
writeln!(f, "On-chain Hash: {}", self.on_chain_hash)?;
74+
writeln!(f, "Executable Hash: {}", self.executable_hash)?;
75+
write!(f, "Repository URL: {}", self.repo_url)
76+
}
77+
}
78+
79+
#[derive(Debug, Serialize, Deserialize)]
80+
pub struct RemoteStatusResponse {
81+
pub signer: String,
82+
pub is_verified: bool,
83+
pub on_chain_hash: String,
84+
pub executable_hash: String,
85+
pub repo_url: String,
86+
pub commit: String,
87+
pub last_verified_at: String,
88+
}
89+
90+
impl std::fmt::Display for RemoteStatusResponse {
91+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92+
writeln!(f, "Verification Status for Signer: {}", self.signer)?;
93+
writeln!(
94+
f,
95+
"Verified: {}",
96+
if self.is_verified { "✅" } else { "❌" }
97+
)?;
98+
writeln!(f, "On-chain Hash: {}", self.on_chain_hash)?;
99+
writeln!(f, "Executable Hash: {}", self.executable_hash)?;
100+
writeln!(f, "Repository URL: {}", self.repo_url)?;
101+
writeln!(f, "Commit: {}", self.commit)?;
102+
write!(f, "Last Verified: {}", self.last_verified_at)
103+
}
104+
}
105+
106+
#[derive(Debug, Serialize, Deserialize)]
107+
pub struct RemoteStatusResponseWrapper(Vec<RemoteStatusResponse>);
108+
109+
impl std::fmt::Display for RemoteStatusResponseWrapper {
110+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111+
for (i, response) in self.0.iter().enumerate() {
112+
if i > 0 {
113+
writeln!(f)?;
114+
writeln!(
115+
f,
116+
"----------------------------------------------------------------"
117+
)?;
118+
}
119+
write!(f, "{}", response)?;
120+
}
121+
Ok(())
122+
}
123+
}

src/main.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::anyhow;
2+
use api::{get_remote_job, get_remote_status, send_job_with_uploader_to_remote};
23
use cargo_lock::Lockfile;
34
use cargo_toml::Manifest;
45
use clap::{App, AppSettings, Arg, SubCommand};
@@ -216,6 +217,35 @@ async fn main() -> anyhow::Result<()> {
216217
.help("Signer to get the PDA for")
217218
)
218219
)
220+
.subcommand(SubCommand::with_name("remote")
221+
.about("Send a command to a remote machine")
222+
.setting(AppSettings::SubcommandRequiredElseHelp)
223+
.subcommand(SubCommand::with_name("get-status")
224+
.about("Get the verification status of a program")
225+
.arg(Arg::with_name("program-id")
226+
.long("program-id")
227+
.required(true)
228+
.takes_value(true)
229+
.help("The program address to fetch verification status for")))
230+
231+
.subcommand(SubCommand::with_name("get-job")
232+
.about("Get the status of a verification job")
233+
.arg(Arg::with_name("job-id")
234+
.long("job-id")
235+
.required(true)
236+
.takes_value(true)))
237+
.subcommand(SubCommand::with_name("submit-job")
238+
.about("Submit a verification job with with on-chain information")
239+
.arg(Arg::with_name("program-id")
240+
.long("program-id")
241+
.required(true)
242+
.takes_value(true))
243+
.arg(Arg::with_name("uploader")
244+
.long("uploader")
245+
.required(true)
246+
.takes_value(true)
247+
.help("This is the address that uploaded verified build information for the program-id")))
248+
)
219249
.get_matches();
220250

221251
let connection = resolve_rpc_url(matches.value_of("url").map(|s| s.to_string()))?;
@@ -339,6 +369,28 @@ async fn main() -> anyhow::Result<()> {
339369
let signer = sub_m.value_of("signer").map(|s| s.to_string());
340370
print_program_pda(Pubkey::try_from(program_id)?, signer, &connection).await
341371
}
372+
("remote", Some(sub_m)) => match sub_m.subcommand() {
373+
("get-status", Some(sub_m)) => {
374+
let program_id = sub_m.value_of("program-id").unwrap();
375+
get_remote_status(Pubkey::try_from(program_id)?).await
376+
}
377+
("get-job", Some(sub_m)) => {
378+
let job_id = sub_m.value_of("job-id").unwrap();
379+
get_remote_job(job_id).await
380+
}
381+
("submit-job", Some(sub_m)) => {
382+
let program_id = sub_m.value_of("program-id").unwrap();
383+
let uploader = sub_m.value_of("uploader").unwrap();
384+
385+
send_job_with_uploader_to_remote(
386+
&connection,
387+
&Pubkey::try_from(program_id)?,
388+
&Pubkey::try_from(uploader)?,
389+
)
390+
.await
391+
}
392+
_ => unreachable!(),
393+
},
342394
// Handle other subcommands in a similar manner, for now let's panic
343395
_ => panic!(
344396
"Unknown subcommand: {:?}\nUse '--help' to see available commands",

src/solana_program.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,11 @@ pub async fn get_program_pda(
341341
.map_err(|err| anyhow!("Unable to parse build params: {}", err))?,
342342
))
343343
} else {
344-
Err(anyhow!("PDA not found"))
344+
Err(anyhow!(
345+
"PDA not found for {:?} and uploader {:?}. Make sure you've uploaded the PDA to mainnet.",
346+
program_id,
347+
signer_pubkey
348+
))
345349
}
346350
}
347351

0 commit comments

Comments
 (0)