unc_sandbox_utils/
lib.rs

1use anyhow::{anyhow, Context};
2use binary_install::Cache;
3use chrono::Utc;
4use fs2::FileExt;
5use tokio::process::{Child, Command};
6
7use std::fs::File;
8use std::path::{Path, PathBuf};
9
10pub mod sync;
11
12// The current version of the sandbox node we want to point to.
13// Should be updated to the latest release of Utility.
14// Currently pointing to Utility@v0.12.3 released on April 9, 2024
15pub const DEFAULT_UNC_SANDBOX_VERSION: &str = "0.12.3/47d8a7456dd8cd784bc4a37db2127615c83ab997";
16
17const fn platform() -> Option<&'static str> {
18    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
19    return Some("Linux-x86_64");
20
21    #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
22    return Some("Darwin-x86_64");
23
24    #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
25    return Some("Darwin-arm64");
26
27    #[cfg(all(
28        not(target_os = "macos"),
29        not(all(target_os = "linux", target_arch = "x86_64"))
30    ))]
31    return None;
32}
33
34fn local_addr(port: u16) -> String {
35    format!("0.0.0.0:{}", port)
36}
37
38// if the `SANDBOX_ARTIFACT_URL` env var is set, we short-circuit and use that.
39fn bin_url(version: &str) -> Option<String> {
40    if let Ok(val) = std::env::var("SANDBOX_ARTIFACT_URL") {
41        return Some(val);
42    }
43
44    Some(format!(
45        "https://unc-s3.jongun2038.win/{}/{}/unc-node-sandbox.tar.gz",
46        platform()?,
47        version,
48    ))
49}
50
51fn download_path() -> PathBuf {
52    if cfg!(feature = "global_install") {
53        let mut buf = home::home_dir().expect("could not retrieve home_dir");
54        buf.push(".unc");
55        buf
56    } else {
57        PathBuf::from(env!("OUT_DIR"))
58    }
59}
60
61/// Returns a path to the binary in the form of {home}/.unc/unc-node-sandbox-{hash}/unc-node-sandbox
62pub fn bin_path() -> anyhow::Result<PathBuf> {
63    if let Ok(path) = std::env::var("UNC_SANDBOX_BIN_PATH") {
64        let path = PathBuf::from(path);
65        if !path.exists() {
66            anyhow::bail!("binary {} does not exist", path.display());
67        }
68        return Ok(path);
69    }
70
71    let mut buf = download_path();
72    buf.push("unc-node-sandbox");
73
74    Ok(buf)
75}
76
77/// Install the sandbox node given the version, which is either a commit hash or tagged version
78/// number from the nearcore project. Note that commits pushed to master within the latest 12h
79/// will likely not have the binaries made available quite yet.
80pub fn install_with_version(version: &str) -> anyhow::Result<PathBuf> {
81    // Download binary into temp dir
82    let tmp_dir = format!("unc-node-sandbox-{}", Utc::now());
83    let dl_cache = Cache::at(&download_path());
84    let bin_path = bin_url(version)
85        .ok_or_else(|| anyhow!("Unsupported platform: only linux-x86 and macos are supported"))?;
86    let dl = dl_cache
87        .download(true, &tmp_dir, &["unc-node-sandbox"], &bin_path)
88        .map_err(anyhow::Error::msg)
89        .with_context(|| "unable to download unc-node-sandbox")?
90        .ok_or_else(|| anyhow!("Could not install unc-node-sandbox"))?;
91
92    let path = dl.binary("unc-node-sandbox").map_err(anyhow::Error::msg)?;
93
94    // Move unc-node-sandbox binary to correct location from temp folder.
95    let dest = download_path().join("unc-node-sandbox");
96    std::fs::rename(path, &dest)?;
97
98    Ok(dest)
99}
100
101/// Installs sandbox node with the default version. This is a version that is usually stable
102/// and has landed into mainnet to reflect the latest stable features and fixes.
103pub fn install() -> anyhow::Result<PathBuf> {
104    install_with_version(DEFAULT_UNC_SANDBOX_VERSION)
105}
106
107fn installable(bin_path: &Path) -> anyhow::Result<Option<std::fs::File>> {
108    // Sandbox bin already exists
109    if bin_path.exists() {
110        return Ok(None);
111    }
112
113    let mut lockpath = bin_path.to_path_buf();
114    lockpath.set_extension("lock");
115
116    // Acquire the lockfile
117    let lockfile = File::create(lockpath)?;
118    lockfile.lock_exclusive()?;
119
120    // Check again after acquiring if no one has written to the dest path
121    if bin_path.exists() {
122        Ok(None)
123    } else {
124        Ok(Some(lockfile))
125    }
126}
127
128pub fn ensure_sandbox_bin() -> anyhow::Result<PathBuf> {
129    ensure_sandbox_bin_with_version(DEFAULT_UNC_SANDBOX_VERSION)
130}
131
132pub fn run_with_options(options: &[&str]) -> anyhow::Result<Child> {
133    let bin_path = crate::ensure_sandbox_bin()?;
134    Command::new(&bin_path)
135        .args(options)
136        .envs(crate::log_vars())
137        .spawn()
138        .with_context(|| format!("failed to run sandbox using '{}'", bin_path.display()))
139}
140
141pub fn run(home_dir: impl AsRef<Path>, rpc_port: u16, network_port: u16) -> anyhow::Result<Child> {
142    run_with_version(
143        home_dir,
144        rpc_port,
145        network_port,
146        DEFAULT_UNC_SANDBOX_VERSION,
147    )
148}
149
150pub fn init(home_dir: impl AsRef<Path>) -> anyhow::Result<Child> {
151    init_with_version(home_dir, DEFAULT_UNC_SANDBOX_VERSION)
152}
153
154pub fn ensure_sandbox_bin_with_version(version: &str) -> anyhow::Result<PathBuf> {
155    let mut bin_path = bin_path()?;
156    if let Some(lockfile) = installable(&bin_path)? {
157        bin_path = install_with_version(version)?;
158        println!("Installed unc-node-sandbox into {}", bin_path.to_str().unwrap());
159        std::env::set_var("UNC_SANDBOX_BIN_PATH", bin_path.as_os_str());
160        lockfile.unlock()?;
161    }
162    Ok(bin_path)
163}
164
165pub fn run_with_options_with_version(options: &[&str], version: &str) -> anyhow::Result<Child> {
166    let bin_path = crate::ensure_sandbox_bin_with_version(version)?;
167    Command::new(&bin_path)
168        .args(options)
169        .envs(crate::log_vars())
170        .spawn()
171        .with_context(|| format!("failed to run sandbox using '{}'", bin_path.display()))
172}
173
174pub fn run_with_version(
175    home_dir: impl AsRef<Path>,
176    rpc_port: u16,
177    network_port: u16,
178    version: &str,
179) -> anyhow::Result<Child> {
180    let home_dir = home_dir.as_ref().to_str().unwrap();
181    run_with_options_with_version(
182        &[
183            "--home",
184            home_dir,
185            "run",
186            "--rpc-addr",
187            &local_addr(rpc_port),
188            "--network-addr",
189            &local_addr(network_port),
190        ],
191        version,
192    )
193}
194
195pub fn init_with_version(home_dir: impl AsRef<Path>, version: &str) -> anyhow::Result<Child> {
196    let bin_path = ensure_sandbox_bin_with_version(version)?;
197    let home_dir = home_dir.as_ref().to_str().unwrap();
198    Command::new(&bin_path)
199        .envs(log_vars())
200        .args(["--home", home_dir, "init", "--fast"])
201        .spawn()
202        .with_context(|| format!("failed to init sandbox using '{}'", bin_path.display()))
203}
204
205fn log_vars() -> Vec<(String, String)> {
206    let mut vars = Vec::new();
207    if let Ok(val) = std::env::var("UNC_SANDBOX_LOG") {
208        vars.push(("RUST_LOG".into(), val));
209    }
210    if let Ok(val) = std::env::var("UNC_SANDBOX_LOG_STYLE") {
211        vars.push(("RUST_LOG_STYLE".into(), val));
212    }
213    vars
214}