use super::Error;
use is_executable::IsExecutable;
use std::path::PathBuf;
#[cfg(test)]
thread_local! {
static TMP_DIR: std::cell::RefCell<Option<tempfile::TempDir>> = std::cell::RefCell::new(None);
}
#[cfg(test)]
fn workers_exe_path_override() -> Option<PathBuf> {
TMP_DIR.with_borrow(|t| t.as_ref().map(|t| t.path().join("usr/bin")))
}
#[cfg(test)]
fn workers_lib_path_override() -> Option<PathBuf> {
TMP_DIR.with_borrow(|t| t.as_ref().map(|t| t.path().join("usr/lib/polkadot")))
}
pub fn determine_workers_paths(
given_workers_path: Option<PathBuf>,
workers_names: Option<(String, String)>,
node_version: Option<String>,
) -> Result<(PathBuf, PathBuf), Error> {
let mut workers_paths = list_workers_paths(given_workers_path.clone(), workers_names.clone())?;
if workers_paths.is_empty() {
let current_exe_path = get_exe_path()?;
return Err(Error::MissingWorkerBinaries {
given_workers_path,
current_exe_path,
workers_names,
})
} else if workers_paths.len() > 1 {
log::warn!("multiple sets of worker binaries found ({:?})", workers_paths,);
}
let (prep_worker_path, exec_worker_path) = workers_paths.swap_remove(0);
if !prep_worker_path.is_executable() || !exec_worker_path.is_executable() {
return Err(Error::InvalidWorkerBinaries { prep_worker_path, exec_worker_path })
}
if let Some(node_version) = node_version {
let worker_version = polkadot_node_core_pvf::get_worker_version(&prep_worker_path)?;
if worker_version != node_version {
return Err(Error::WorkerBinaryVersionMismatch {
worker_version,
node_version,
worker_path: prep_worker_path,
})
}
let worker_version = polkadot_node_core_pvf::get_worker_version(&exec_worker_path)?;
if worker_version != node_version {
return Err(Error::WorkerBinaryVersionMismatch {
worker_version,
node_version,
worker_path: exec_worker_path,
})
}
} else {
log::warn!("Skipping node/worker version checks. This could result in incorrect behavior in PVF workers.");
}
Ok((prep_worker_path, exec_worker_path))
}
fn list_workers_paths(
given_workers_path: Option<PathBuf>,
workers_names: Option<(String, String)>,
) -> Result<Vec<(PathBuf, PathBuf)>, Error> {
if let Some(path) = given_workers_path {
log::trace!("Using explicitly provided workers path {:?}", path);
if path.is_executable() {
return Ok(vec![(path.clone(), path)])
}
let (prep_worker, exec_worker) = build_worker_paths(path, workers_names);
return if prep_worker.exists() && exec_worker.exists() {
Ok(vec![(prep_worker, exec_worker)])
} else {
Ok(vec![])
}
}
let mut workers_paths = vec![];
{
let exe_path = get_exe_path()?;
let (prep_worker, exec_worker) =
build_worker_paths(exe_path.clone(), workers_names.clone());
let (prep_worker_exists, exec_worker_exists) = (prep_worker.exists(), exec_worker.exists());
if prep_worker_exists && exec_worker_exists {
log::trace!("Worker binaries found at current exe path: {:?}", exe_path);
workers_paths.push((prep_worker, exec_worker));
} else if prep_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", prep_worker, exec_worker);
} else if exec_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", exec_worker, prep_worker);
}
}
{
let lib_path = PathBuf::from("/usr/lib/polkadot");
#[cfg(test)]
let lib_path = if let Some(o) = workers_lib_path_override() { o } else { lib_path };
let (prep_worker, exec_worker) = build_worker_paths(lib_path, workers_names);
let (prep_worker_exists, exec_worker_exists) = (prep_worker.exists(), exec_worker.exists());
if prep_worker_exists && exec_worker_exists {
log::trace!("Worker binaries found at /usr/lib/polkadot");
workers_paths.push((prep_worker, exec_worker));
} else if prep_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", prep_worker, exec_worker);
} else if exec_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", exec_worker, prep_worker);
}
}
Ok(workers_paths)
}
fn get_exe_path() -> Result<PathBuf, Error> {
let mut exe_path = std::env::current_exe()?;
let _ = exe_path.pop(); #[cfg(test)]
if let Some(o) = workers_exe_path_override() {
exe_path = o;
}
Ok(exe_path)
}
fn build_worker_paths(
worker_dir: PathBuf,
workers_names: Option<(String, String)>,
) -> (PathBuf, PathBuf) {
let (prep_worker_name, exec_worker_name) = workers_names.unwrap_or((
polkadot_node_core_pvf::PREPARE_BINARY_NAME.to_string(),
polkadot_node_core_pvf::EXECUTE_BINARY_NAME.to_string(),
));
let mut prep_worker = worker_dir.clone();
prep_worker.push(prep_worker_name);
let mut exec_worker = worker_dir;
exec_worker.push(exec_worker_name);
(prep_worker, exec_worker)
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use std::{fs, os::unix::fs::PermissionsExt, path::Path};
const TEST_NODE_VERSION: &'static str = "v0.1.2";
fn write_worker_exe(path: impl AsRef<Path>) -> Result<(), Box<dyn std::error::Error>> {
let program = get_program(TEST_NODE_VERSION);
fs::write(&path, program)?;
Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?)
}
fn write_worker_exe_invalid_version(
path: impl AsRef<Path>,
version: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let program = get_program(version);
fs::write(&path, program)?;
Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?)
}
fn get_program(version: &str) -> String {
format!(
"#!/usr/bin/env bash
if [[ $# -ne 1 ]] ; then
echo \"unexpected number of arguments: $#\"
exit 1
fi
if [[ \"$1\" != \"--version\" ]] ; then
echo \"unexpected argument: $1\"
exit 1
fi
echo {}
",
version
)
}
fn with_temp_dir_structure(
f: impl FnOnce(PathBuf, PathBuf) -> Result<(), Box<dyn std::error::Error>>,
) -> Result<(), Box<dyn std::error::Error>> {
let tempdir = tempfile::tempdir().unwrap();
let tmp_dir = tempdir.path().to_path_buf();
TMP_DIR.with_borrow_mut(|t| *t = Some(tempdir));
fs::create_dir_all(workers_lib_path_override().unwrap()).unwrap();
fs::create_dir_all(workers_exe_path_override().unwrap()).unwrap();
let custom_path = tmp_dir.join("usr/local/bin");
fs::create_dir_all(&custom_path).unwrap();
f(tmp_dir, workers_exe_path_override().unwrap())
}
#[test]
fn test_given_worker_path() {
with_temp_dir_structure(|tempdir, exe_path| {
let given_workers_path = tempdir.join("usr/local/bin");
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: Some(p1), current_exe_path: p2, workers_names: None }) if p1 == given_workers_path && p2 == exe_path
);
let prepare_worker_path = given_workers_path.join("polkadot-prepare-worker");
write_worker_exe(&prepare_worker_path)?;
fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o644))?;
let execute_worker_path = given_workers_path.join("polkadot-execute-worker");
write_worker_exe(&execute_worker_path)?;
fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o644))?;
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Err(Error::InvalidWorkerBinaries { prep_worker_path: p1, exec_worker_path: p2 }) if p1 == prepare_worker_path && p2 == execute_worker_path
);
fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o744))?;
fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o744))?;
assert_matches!(
determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == prepare_worker_path && p2 == execute_worker_path
);
let given_workers_path = tempdir.join("usr/local/bin/test-worker");
write_worker_exe(&given_workers_path)?;
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == given_workers_path && p2 == given_workers_path
);
Ok(())
})
.unwrap();
}
#[test]
fn missing_workers_paths_throws_error() {
with_temp_dir_structure(|tempdir, exe_path| {
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
let prepare_worker_path = tempdir.join("usr/bin/polkadot-prepare-worker");
write_worker_exe(&prepare_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
fs::remove_file(&prepare_worker_path)?;
let execute_worker_path = tempdir.join("usr/bin/polkadot-execute-worker");
write_worker_exe(&execute_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
fs::remove_file(&execute_worker_path)?;
let prepare_worker_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
write_worker_exe(&prepare_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
fs::remove_file(&prepare_worker_path)?;
let execute_worker_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
write_worker_exe(execute_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
Ok(())
})
.unwrap()
}
#[test]
fn should_find_workers_at_all_locations() {
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_bin_path = tempdir.join("usr/bin/polkadot-prepare-worker");
write_worker_exe(&prepare_worker_bin_path)?;
let execute_worker_bin_path = tempdir.join("usr/bin/polkadot-execute-worker");
write_worker_exe(&execute_worker_bin_path)?;
let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
write_worker_exe(&prepare_worker_lib_path)?;
let execute_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
write_worker_exe(&execute_worker_lib_path)?;
assert_matches!(
list_workers_paths(None, None),
Ok(v) if v == vec![(prepare_worker_bin_path, execute_worker_bin_path), (prepare_worker_lib_path, execute_worker_lib_path)]
);
Ok(())
})
.unwrap();
}
#[test]
fn should_find_workers_with_custom_names_at_all_locations() {
with_temp_dir_structure(|tempdir, _| {
let (prep_worker_name, exec_worker_name) = ("test-prepare", "test-execute");
let prepare_worker_bin_path = tempdir.join("usr/bin").join(prep_worker_name);
write_worker_exe(&prepare_worker_bin_path)?;
let execute_worker_bin_path = tempdir.join("usr/bin").join(exec_worker_name);
write_worker_exe(&execute_worker_bin_path)?;
let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot").join(prep_worker_name);
write_worker_exe(&prepare_worker_lib_path)?;
let execute_worker_lib_path = tempdir.join("usr/lib/polkadot").join(exec_worker_name);
write_worker_exe(&execute_worker_lib_path)?;
assert_matches!(
list_workers_paths(None, Some((prep_worker_name.into(), exec_worker_name.into()))),
Ok(v) if v == vec![(prepare_worker_bin_path, execute_worker_bin_path), (prepare_worker_lib_path, execute_worker_lib_path)]
);
Ok(())
})
.unwrap();
}
#[test]
fn workers_version_mismatch_throws_error() {
let bad_version = "v9.9.9.9";
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_bin_path = tempdir.join("usr/bin/polkadot-prepare-worker");
let execute_worker_bin_path = tempdir.join("usr/bin/polkadot-execute-worker");
write_worker_exe_invalid_version(&prepare_worker_bin_path, bad_version)?;
write_worker_exe(&execute_worker_bin_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
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
);
fs::remove_file(prepare_worker_bin_path)?;
fs::remove_file(execute_worker_bin_path)?;
let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
let execute_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
write_worker_exe(&prepare_worker_lib_path)?;
write_worker_exe_invalid_version(&execute_worker_lib_path, bad_version)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
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
);
let given_workers_path = tempdir.join("usr/local/bin");
let prepare_worker_path = given_workers_path.join("polkadot-prepare-worker");
let execute_worker_path = given_workers_path.join("polkadot-execute-worker");
write_worker_exe_invalid_version(&prepare_worker_path, bad_version)?;
write_worker_exe_invalid_version(&execute_worker_path, bad_version)?;
assert_matches!(
determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())),
Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_path
);
let given_workers_path = tempdir.join("usr/local/bin/test-worker");
write_worker_exe_invalid_version(&given_workers_path, bad_version)?;
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == given_workers_path
);
Ok(())
})
.unwrap();
}
#[test]
fn should_find_valid_workers() {
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_bin_path = tempdir.join("usr/bin/polkadot-prepare-worker");
write_worker_exe(&prepare_worker_bin_path)?;
let execute_worker_bin_path = tempdir.join("usr/bin/polkadot-execute-worker");
write_worker_exe(&execute_worker_bin_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == prepare_worker_bin_path && p2 == execute_worker_bin_path
);
Ok(())
})
.unwrap();
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker");
write_worker_exe(&prepare_worker_lib_path)?;
let execute_worker_lib_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker");
write_worker_exe(&execute_worker_lib_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == prepare_worker_lib_path && p2 == execute_worker_lib_path
);
Ok(())
})
.unwrap();
}
}