1use super::Error;
20use is_executable::IsExecutable;
21use std::path::PathBuf;
22
23#[cfg(test)]
24thread_local! {
25 static TMP_DIR: std::cell::RefCell<Option<tempfile::TempDir>> = std::cell::RefCell::new(None);
26}
27
28#[cfg(test)]
30fn workers_exe_path_override() -> Option<PathBuf> {
31 TMP_DIR.with_borrow(|t| t.as_ref().map(|t| t.path().join("usr/bin")))
32}
33
34#[cfg(test)]
36fn workers_lib_path_override() -> Option<PathBuf> {
37 TMP_DIR.with_borrow(|t| t.as_ref().map(|t| t.path().join("usr/lib/polkadot")))
38}
39
40pub fn determine_workers_paths(
56 given_workers_path: Option<PathBuf>,
57 workers_names: Option<(String, String)>,
58 node_version: Option<String>,
59) -> Result<(PathBuf, PathBuf), Error> {
60 let mut workers_paths = list_workers_paths(given_workers_path.clone(), workers_names.clone())?;
61 if workers_paths.is_empty() {
62 let current_exe_path = get_exe_path()?;
63 return Err(Error::MissingWorkerBinaries {
64 given_workers_path,
65 current_exe_path,
66 workers_names,
67 })
68 } else if workers_paths.len() > 1 {
69 log::warn!("multiple sets of worker binaries found ({:?})", workers_paths,);
70 }
71
72 let (prep_worker_path, exec_worker_path) = workers_paths.swap_remove(0);
73 if !prep_worker_path.is_executable() || !exec_worker_path.is_executable() {
74 return Err(Error::InvalidWorkerBinaries { prep_worker_path, exec_worker_path })
75 }
76
77 if let Some(node_version) = node_version {
79 let worker_version = polkadot_node_core_pvf::get_worker_version(&prep_worker_path)?;
80 if worker_version != node_version {
81 return Err(Error::WorkerBinaryVersionMismatch {
82 worker_version,
83 node_version,
84 worker_path: prep_worker_path,
85 })
86 }
87
88 let worker_version = polkadot_node_core_pvf::get_worker_version(&exec_worker_path)?;
89 if worker_version != node_version {
90 return Err(Error::WorkerBinaryVersionMismatch {
91 worker_version,
92 node_version,
93 worker_path: exec_worker_path,
94 })
95 }
96 } else {
97 log::warn!("Skipping node/worker version checks. This could result in incorrect behavior in PVF workers.");
98 }
99
100 Ok((prep_worker_path, exec_worker_path))
101}
102
103fn list_workers_paths(
106 given_workers_path: Option<PathBuf>,
107 workers_names: Option<(String, String)>,
108) -> Result<Vec<(PathBuf, PathBuf)>, Error> {
109 if let Some(path) = given_workers_path {
110 log::trace!("Using explicitly provided workers path {:?}", path);
111
112 if path.is_executable() {
113 return Ok(vec![(path.clone(), path)])
114 }
115
116 let (prep_worker, exec_worker) = build_worker_paths(path, workers_names);
117
118 return if prep_worker.exists() && exec_worker.exists() {
120 Ok(vec![(prep_worker, exec_worker)])
121 } else {
122 Ok(vec![])
123 }
124 }
125
126 let mut workers_paths = vec![];
129
130 {
132 let exe_path = get_exe_path()?;
133
134 let (prep_worker, exec_worker) =
135 build_worker_paths(exe_path.clone(), workers_names.clone());
136
137 let (prep_worker_exists, exec_worker_exists) = (prep_worker.exists(), exec_worker.exists());
139 if prep_worker_exists && exec_worker_exists {
140 log::trace!("Worker binaries found at current exe path: {:?}", exe_path);
141 workers_paths.push((prep_worker, exec_worker));
142 } else if prep_worker_exists {
143 log::warn!("Worker binary found at {:?} but not {:?}", prep_worker, exec_worker);
144 } else if exec_worker_exists {
145 log::warn!("Worker binary found at {:?} but not {:?}", exec_worker, prep_worker);
146 }
147 }
148
149 {
151 let lib_path = PathBuf::from("/usr/lib/polkadot");
152 #[cfg(test)]
153 let lib_path = if let Some(o) = workers_lib_path_override() { o } else { lib_path };
154
155 let (prep_worker, exec_worker) = build_worker_paths(lib_path, workers_names);
156
157 let (prep_worker_exists, exec_worker_exists) = (prep_worker.exists(), exec_worker.exists());
159 if prep_worker_exists && exec_worker_exists {
160 log::trace!("Worker binaries found at /usr/lib/polkadot");
161 workers_paths.push((prep_worker, exec_worker));
162 } else if prep_worker_exists {
163 log::warn!("Worker binary found at {:?} but not {:?}", prep_worker, exec_worker);
164 } else if exec_worker_exists {
165 log::warn!("Worker binary found at {:?} but not {:?}", exec_worker, prep_worker);
166 }
167 }
168
169 Ok(workers_paths)
170}
171
172fn get_exe_path() -> Result<PathBuf, Error> {
173 let mut exe_path = std::env::current_exe()?;
174 let _ = exe_path.pop(); #[cfg(test)]
176 if let Some(o) = workers_exe_path_override() {
177 exe_path = o;
178 }
179
180 Ok(exe_path)
181}
182
183fn build_worker_paths(
184 worker_dir: PathBuf,
185 workers_names: Option<(String, String)>,
186) -> (PathBuf, PathBuf) {
187 let (prep_worker_name, exec_worker_name) = workers_names.unwrap_or((
188 polkadot_node_core_pvf::PREPARE_BINARY_NAME.to_string(),
189 polkadot_node_core_pvf::EXECUTE_BINARY_NAME.to_string(),
190 ));
191
192 let mut prep_worker = worker_dir.clone();
193 prep_worker.push(prep_worker_name);
194 let mut exec_worker = worker_dir;
195 exec_worker.push(exec_worker_name);
196
197 (prep_worker, exec_worker)
198}
199
200#[cfg(test)]
203mod tests {
204 use super::*;
205
206 use assert_matches::assert_matches;
207 use std::{fs, os::unix::fs::PermissionsExt, path::Path};
208
209 const TEST_NODE_VERSION: &'static str = "v0.1.2";
210
211 fn write_worker_exe(path: impl AsRef<Path>) -> Result<(), Box<dyn std::error::Error>> {
213 let program = get_program(TEST_NODE_VERSION);
214 fs::write(&path, program)?;
215 Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?)
216 }
217
218 fn write_worker_exe_invalid_version(
219 path: impl AsRef<Path>,
220 version: &str,
221 ) -> Result<(), Box<dyn std::error::Error>> {
222 let program = get_program(version);
223 fs::write(&path, program)?;
224 Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?)
225 }
226
227 fn get_program(version: &str) -> String {
228 format!(
229 "#!/usr/bin/env bash
230
231if [[ $# -ne 1 ]] ; then
232 echo \"unexpected number of arguments: $#\"
233 exit 1
234fi
235
236if [[ \"$1\" != \"--version\" ]] ; then
237 echo \"unexpected argument: $1\"
238 exit 1
239fi
240
241echo {}
242",
243 version
244 )
245 }
246
247 fn with_temp_dir_structure(
250 f: impl FnOnce(PathBuf, PathBuf) -> Result<(), Box<dyn std::error::Error>>,
251 ) -> Result<(), Box<dyn std::error::Error>> {
252 let tempdir = tempfile::tempdir().unwrap();
255 let tmp_dir = tempdir.path().to_path_buf();
256 TMP_DIR.with_borrow_mut(|t| *t = Some(tempdir));
257
258 fs::create_dir_all(workers_lib_path_override().unwrap()).unwrap();
259 fs::create_dir_all(workers_exe_path_override().unwrap()).unwrap();
260
261 let custom_path = tmp_dir.join("usr/local/bin");
262 fs::create_dir_all(&custom_path).unwrap();
264
265 f(tmp_dir, workers_exe_path_override().unwrap())
266 }
267
268 #[test]
269 fn test_given_worker_path() {
270 with_temp_dir_structure(|tempdir, exe_path| {
271 let given_workers_path = tempdir.join("usr/local/bin");
272
273 assert_matches!(
275 determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
276 Err(Error::MissingWorkerBinaries { given_workers_path: Some(p1), current_exe_path: p2, workers_names: None }) if p1 == given_workers_path && p2 == exe_path
277 );
278
279 let prepare_worker_path = given_workers_path.join("polkadot-prepare-worker");
281 write_worker_exe(&prepare_worker_path)?;
282 fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o644))?;
283 let execute_worker_path = given_workers_path.join("polkadot-execute-worker");
284 write_worker_exe(&execute_worker_path)?;
285 fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o644))?;
286 assert_matches!(
287 determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
288 Err(Error::InvalidWorkerBinaries { prep_worker_path: p1, exec_worker_path: p2 }) if p1 == prepare_worker_path && p2 == execute_worker_path
289 );
290
291 fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o744))?;
293 fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o744))?;
294 assert_matches!(
295 determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())),
296 Ok((p1, p2)) if p1 == prepare_worker_path && p2 == execute_worker_path
297 );
298
299 let given_workers_path = tempdir.join("usr/local/bin/test-worker");
301 write_worker_exe(&given_workers_path)?;
302 assert_matches!(
303 determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
304 Ok((p1, p2)) if p1 == given_workers_path && p2 == given_workers_path
305 );
306
307 Ok(())
308 })
309 .unwrap();
310 }
311
312 #[test]
313 fn missing_workers_paths_throws_error() {
314 with_temp_dir_structure(|tempdir, exe_path| {
315 assert_matches!(
317 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
318 Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
319 );
320
321 let prepare_worker_path = tempdir.join("usr/bin/polkadot-prepare-worker");
323 write_worker_exe(&prepare_worker_path)?;
324 assert_matches!(
325 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
326 Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
327 );
328
329 fs::remove_file(&prepare_worker_path)?;
331 let execute_worker_path = tempdir.join("usr/bin/polkadot-execute-worker");
332 write_worker_exe(&execute_worker_path)?;
333 assert_matches!(
334 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
335 Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
336 );
337
338 fs::remove_file(&execute_worker_path)?;
340 let prepare_worker_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
341 write_worker_exe(&prepare_worker_path)?;
342 assert_matches!(
343 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
344 Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
345 );
346
347 fs::remove_file(&prepare_worker_path)?;
349 let execute_worker_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
350 write_worker_exe(execute_worker_path)?;
351 assert_matches!(
352 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
353 Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
354 );
355
356 Ok(())
357 })
358 .unwrap()
359 }
360
361 #[test]
362 fn should_find_workers_at_all_locations() {
363 with_temp_dir_structure(|tempdir, _| {
364 let prepare_worker_bin_path = tempdir.join("usr/bin/polkadot-prepare-worker");
365 write_worker_exe(&prepare_worker_bin_path)?;
366
367 let execute_worker_bin_path = tempdir.join("usr/bin/polkadot-execute-worker");
368 write_worker_exe(&execute_worker_bin_path)?;
369
370 let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
371 write_worker_exe(&prepare_worker_lib_path)?;
372
373 let execute_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
374 write_worker_exe(&execute_worker_lib_path)?;
375
376 assert_matches!(
377 list_workers_paths(None, None),
378 Ok(v) if v == vec![(prepare_worker_bin_path, execute_worker_bin_path), (prepare_worker_lib_path, execute_worker_lib_path)]
379 );
380
381 Ok(())
382 })
383 .unwrap();
384 }
385
386 #[test]
387 fn should_find_workers_with_custom_names_at_all_locations() {
388 with_temp_dir_structure(|tempdir, _| {
389 let (prep_worker_name, exec_worker_name) = ("test-prepare", "test-execute");
390
391 let prepare_worker_bin_path = tempdir.join("usr/bin").join(prep_worker_name);
392 write_worker_exe(&prepare_worker_bin_path)?;
393
394 let execute_worker_bin_path = tempdir.join("usr/bin").join(exec_worker_name);
395 write_worker_exe(&execute_worker_bin_path)?;
396
397 let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot").join(prep_worker_name);
398 write_worker_exe(&prepare_worker_lib_path)?;
399
400 let execute_worker_lib_path = tempdir.join("usr/lib/polkadot").join(exec_worker_name);
401 write_worker_exe(&execute_worker_lib_path)?;
402
403 assert_matches!(
404 list_workers_paths(None, Some((prep_worker_name.into(), exec_worker_name.into()))),
405 Ok(v) if v == vec![(prepare_worker_bin_path, execute_worker_bin_path), (prepare_worker_lib_path, execute_worker_lib_path)]
406 );
407
408 Ok(())
409 })
410 .unwrap();
411 }
412
413 #[test]
414 fn workers_version_mismatch_throws_error() {
415 let bad_version = "v9.9.9.9";
416
417 with_temp_dir_structure(|tempdir, _| {
418 let prepare_worker_bin_path = tempdir.join("usr/bin/polkadot-prepare-worker");
420 let execute_worker_bin_path = tempdir.join("usr/bin/polkadot-execute-worker");
421 write_worker_exe_invalid_version(&prepare_worker_bin_path, bad_version)?;
422 write_worker_exe(&execute_worker_bin_path)?;
423 assert_matches!(
424 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
425 Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_bin_path
426 );
427
428 fs::remove_file(prepare_worker_bin_path)?;
430 fs::remove_file(execute_worker_bin_path)?;
431 let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
432 let execute_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
433 write_worker_exe(&prepare_worker_lib_path)?;
434 write_worker_exe_invalid_version(&execute_worker_lib_path, bad_version)?;
435 assert_matches!(
436 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
437 Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == execute_worker_lib_path
438 );
439
440 let given_workers_path = tempdir.join("usr/local/bin");
442 let prepare_worker_path = given_workers_path.join("polkadot-prepare-worker");
443 let execute_worker_path = given_workers_path.join("polkadot-execute-worker");
444 write_worker_exe_invalid_version(&prepare_worker_path, bad_version)?;
445 write_worker_exe_invalid_version(&execute_worker_path, bad_version)?;
446 assert_matches!(
447 determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())),
448 Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_path
449 );
450
451 let given_workers_path = tempdir.join("usr/local/bin/test-worker");
453 write_worker_exe_invalid_version(&given_workers_path, bad_version)?;
454 assert_matches!(
455 determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
456 Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == given_workers_path
457 );
458
459 Ok(())
460 })
461 .unwrap();
462 }
463
464 #[test]
465 fn should_find_valid_workers() {
466 with_temp_dir_structure(|tempdir, _| {
468 let prepare_worker_bin_path = tempdir.join("usr/bin/polkadot-prepare-worker");
469 write_worker_exe(&prepare_worker_bin_path)?;
470
471 let execute_worker_bin_path = tempdir.join("usr/bin/polkadot-execute-worker");
472 write_worker_exe(&execute_worker_bin_path)?;
473
474 assert_matches!(
475 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
476 Ok((p1, p2)) if p1 == prepare_worker_bin_path && p2 == execute_worker_bin_path
477 );
478
479 Ok(())
480 })
481 .unwrap();
482
483 with_temp_dir_structure(|tempdir, _| {
485 let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
486 write_worker_exe(&prepare_worker_lib_path)?;
487
488 let execute_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
489 write_worker_exe(&execute_worker_lib_path)?;
490
491 assert_matches!(
492 determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
493 Ok((p1, p2)) if p1 == prepare_worker_lib_path && p2 == execute_worker_lib_path
494 );
495
496 Ok(())
497 })
498 .unwrap();
499 }
500}