casper_client/
verification.rs1use std::{cmp::min, io, path::Path};
2
3use bytes::{BufMut, Bytes, BytesMut};
4use flate2::{write::GzEncoder, Compression};
5use reqwest::{
6 header::{HeaderMap, HeaderValue, CONTENT_TYPE},
7 Client, ClientBuilder, StatusCode,
8};
9use tar::Builder as TarBuilder;
10use tokio::time::{sleep, Duration};
11
12use crate::{
13 verification_types::{
14 VerificationDetails, VerificationRequest, VerificationResult, VerificationStatus,
15 },
16 Error, Verbosity,
17};
18
19const MAX_RETRIES: u32 = 10;
20const BASE_DELAY: Duration = Duration::from_secs(3);
21const MAX_DELAY: Duration = Duration::from_secs(300);
22
23static GIT_DIR_NAME: &str = ".git";
24static TARGET_DIR_NAME: &str = "target";
25
26pub fn build_archive(path: &Path) -> Result<Bytes, io::Error> {
40 let buffer = BytesMut::new().writer();
41 let encoder = GzEncoder::new(buffer, Compression::best());
42 let mut archive = TarBuilder::new(encoder);
43
44 for entry in path.read_dir()?.flatten() {
45 let file_name = entry.file_name();
46 if file_name == TARGET_DIR_NAME || file_name == GIT_DIR_NAME {
48 continue;
49 }
50 let full_path = entry.path();
51 if full_path.is_dir() {
52 archive.append_dir_all(&file_name, &full_path)?;
53 } else {
54 archive.append_path_with_name(&full_path, &file_name)?;
55 }
56 }
57
58 let encoder = archive.into_inner()?;
59 let buffer = encoder.finish()?;
60 Ok(buffer.into_inner().freeze())
61}
62
63pub async fn send_verification_request(
79 hash_str: &str,
80 base_url: &str,
81 code_archive: String,
82 verbosity: Verbosity,
83) -> Result<VerificationDetails, Error> {
84 let verification_request = VerificationRequest {
85 hash: hash_str.to_string(),
86 code_archive,
87 };
88
89 fn make_client() -> reqwest::Result<Client> {
90 let mut headers = HeaderMap::new();
91 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
92
93 let builder = ClientBuilder::new()
94 .default_headers(headers)
95 .user_agent("casper-client-rs");
96
97 #[cfg(not(target_arch = "wasm32"))]
99 let builder = builder.pool_max_idle_per_host(0);
100
101 builder.build()
102 }
103
104 let Ok(http_client) = make_client() else {
105 eprintln!("Failed to build HTTP client");
106 return Err(Error::FailedToConstructHttpClient);
107 };
108
109 if verbosity == Verbosity::Medium || verbosity == Verbosity::High {
110 println!("Sending verification request");
111 }
112
113 let url = base_url.to_string() + "/verification";
114 let response = match http_client
115 .post(url)
116 .json(&verification_request)
117 .send()
118 .await
119 {
120 Ok(response) => response,
121 Err(error) => {
122 eprintln!("Cannot send verification request: {error:?}");
123 return Err(Error::ContractVerificationFailed);
124 }
125 };
126
127 match response.status() {
128 StatusCode::OK => {
129 if verbosity == Verbosity::Medium || verbosity == Verbosity::High {
130 println!("Sent verification request",);
131 }
132 }
133 status => {
134 eprintln!("Verification failed with status {status}");
135 }
136 }
137
138 wait_for_verification_finished(base_url, &http_client, hash_str, verbosity).await;
139
140 if verbosity == Verbosity::Medium || verbosity == Verbosity::High {
141 println!("Getting verification details...");
142 }
143
144 let details_url = format!("{}/verification/{}/details", base_url, hash_str);
145 match http_client.get(details_url).send().await {
146 Ok(response) => response.json().await.map_err(|err| {
147 eprintln!("Failed to parse JSON {err}");
148 Error::ContractVerificationFailed
149 }),
150 Err(error) => {
151 eprintln!("Cannot get verification details: {error:?}");
152 Err(Error::ContractVerificationFailed)
153 }
154 }
155}
156
157async fn wait_for_verification_finished(
159 base_url: &str,
160 http_client: &Client,
161 hash_str: &str,
162 verbosity: Verbosity,
163) {
164 let mut retries = MAX_RETRIES;
165 let mut delay = BASE_DELAY;
166
167 while retries != 0 {
168 sleep(delay).await;
169
170 match get_verification_status(base_url, http_client, hash_str).await {
171 Ok(status) => {
172 if verbosity == Verbosity::Medium || verbosity == Verbosity::High {
173 println!("Verification status: {status:?}");
174 }
175 if status == VerificationStatus::Verified || status == VerificationStatus::Failed {
176 break;
177 }
178 }
179 Err(error) => {
180 eprintln!("Cannot get verification status: {error:?}");
181 break;
182 }
183 };
184
185 retries -= 1;
186 delay = min(delay * 2, MAX_DELAY);
187 }
188}
189
190async fn get_verification_status(
192 base_url: &str,
193 http_client: &Client,
194 hash_str: &str,
195) -> Result<VerificationStatus, Error> {
196 let status_url = format!("{}/verification/{}/status", base_url, hash_str);
197 let response = match http_client.get(status_url).send().await {
198 Ok(response) => response,
199 Err(error) => {
200 eprintln!("Failed to fetch verification status: {error:?}");
201 return Err(Error::ContractVerificationFailed);
202 }
203 };
204
205 match response.status() {
206 StatusCode::OK => {
207 let result: VerificationResult = response.json().await.map_err(|err| {
208 eprintln!("Failed to parse JSON for verification status, {err}");
209 Error::ContractVerificationFailed
210 })?;
211 Ok(result.status)
212 }
213 status => {
214 eprintln!("Verification status not found, {status}");
215 Err(Error::ContractVerificationFailed)
216 }
217 }
218}