diff --git a/Cargo.lock b/Cargo.lock index 13581def3da..d7b33e3290e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,16 @@ dependencies = [ "serde", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.9.1" @@ -940,6 +950,7 @@ name = "forc" version = "0.3.3" dependencies = [ "annotate-snippets", + "ansi_term 0.12.1", "anyhow", "dirs 3.0.2", "flate2", @@ -968,6 +979,7 @@ dependencies = [ "toml", "ureq", "uwuify", + "warp", "whoami", ] @@ -1264,7 +1276,7 @@ checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.3+wasi-snapshot-preview1", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -1331,6 +1343,31 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "headers" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c4eb0471fcb85846d8b0690695ef354f9afb11cb03cac2e1d7c9253351afb0" +dependencies = [ + "base64", + "bitflags", + "bytes 1.1.0", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.3" @@ -1577,9 +1614,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -1722,7 +1759,7 @@ dependencies = [ "tokio", "tokio-util", "tower-service", - "twoway", + "twoway 0.2.2", ] [[package]] @@ -1812,6 +1849,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand 0.8.4", + "safemem", + "tempfile", + "twoway 0.1.8", +] + [[package]] name = "nanoid" version = "0.4.0" @@ -2132,6 +2187,12 @@ dependencies = [ "unicode-xid 0.2.2", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "0.6.13" @@ -2290,6 +2351,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "reqwest" version = "0.11.9" @@ -2428,6 +2498,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "schannel" version = "0.1.19" @@ -2464,6 +2540,12 @@ dependencies = [ "syn 1.0.85", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2968,6 +3050,20 @@ dependencies = [ "xattr", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall 0.2.10", + "remove_dir_all", + "winapi", +] + [[package]] name = "term" version = "0.5.2" @@ -3171,6 +3267,30 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.6.9" @@ -3249,6 +3369,34 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes 1.1.0", + "http", + "httparse", + "log", + "rand 0.8.4", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "twoway" version = "0.2.2" @@ -3380,6 +3528,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8-ranges" version = "1.0.4" @@ -3448,6 +3602,36 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3456,15 +3640,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.3+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a2e384a3f170b0c7543787a91411175b71afd56ba4d3a0ae5678d4e2243c0e" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "serde", @@ -3474,9 +3658,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -3489,9 +3673,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3501,9 +3685,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote 1.0.14", "wasm-bindgen-macro-support", @@ -3511,9 +3695,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2 1.0.36", "quote 1.0.14", @@ -3524,15 +3708,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/forc/Cargo.toml b/forc/Cargo.toml index f8237a6a4d5..b89bb07a61e 100644 --- a/forc/Cargo.toml +++ b/forc/Cargo.toml @@ -10,6 +10,7 @@ description = "Fuel Orchestrator." [dependencies] annotate-snippets = { version = "0.9", features = ["color"] } +ansi_term = "0.12" anyhow = "1.0.41" dirs = "3.0.2" flate2 = "1.0.20" @@ -37,6 +38,7 @@ termcolor = "1.1" tokio = { version = "1.8.0", features = ["macros", "rt-multi-thread", "process"] } toml = "0.5" ureq = "2.4" +warp = "0.3" whoami = "1.1" uwuify = { version = "^0.2", optional = true } diff --git a/forc/src/cli/commands/explorer.rs b/forc/src/cli/commands/explorer.rs new file mode 100644 index 00000000000..acb6a6e1a11 --- /dev/null +++ b/forc/src/cli/commands/explorer.rs @@ -0,0 +1,24 @@ +use crate::ops::forc_explorer; +use structopt::StructOpt; + +/// Run the network explorer. +#[derive(Debug, StructOpt)] +pub struct Command { + /// The port number + #[structopt(short = "p", long = "port", default_value = "3030")] + pub port: String, + #[structopt(subcommand)] // Note that we mark a field as a subcommand + pub clean: Option, +} + +#[derive(Debug, StructOpt)] +pub enum CleanCommand { + Clean, +} + +pub(crate) async fn exec(_command: Command) -> Result<(), String> { + match forc_explorer::exec(_command).await { + Err(e) => Err(e.to_string()), + _ => Ok(()), + } +} diff --git a/forc/src/cli/commands/mod.rs b/forc/src/cli/commands/mod.rs index 3b6909e902e..6762ecacb2b 100644 --- a/forc/src/cli/commands/mod.rs +++ b/forc/src/cli/commands/mod.rs @@ -1,6 +1,7 @@ pub mod addr2line; pub mod build; pub mod deploy; +pub mod explorer; pub mod format; pub mod init; pub mod json_abi; diff --git a/forc/src/cli/mod.rs b/forc/src/cli/mod.rs index e4a9b69e290..b93d8513c42 100644 --- a/forc/src/cli/mod.rs +++ b/forc/src/cli/mod.rs @@ -2,12 +2,14 @@ use structopt::StructOpt; mod commands; use self::commands::{ - addr2line, build, deploy, format, init, json_abi, lsp, parse_bytecode, run, test, update, + addr2line, build, deploy, explorer, format, init, json_abi, lsp, parse_bytecode, run, test, + update, }; use addr2line::Command as Addr2LineCommand; pub use build::Command as BuildCommand; pub use deploy::Command as DeployCommand; +pub use explorer::Command as ExplorerCommand; pub use format::Command as FormatCommand; use init::Command as InitCommand; pub use json_abi::Command as JsonAbiCommand; @@ -31,6 +33,7 @@ enum Forc { Addr2Line(Addr2LineCommand), Build(BuildCommand), Deploy(DeployCommand), + Explorer(ExplorerCommand), #[structopt(name = "fmt")] Format(FormatCommand), Init(InitCommand), @@ -48,6 +51,7 @@ pub(crate) async fn run_cli() -> Result<(), String> { Forc::Addr2Line(command) => addr2line::exec(command), Forc::Build(command) => build::exec(command), Forc::Deploy(command) => deploy::exec(command).await, + Forc::Explorer(command) => explorer::exec(command).await, Forc::Format(command) => format::exec(command), Forc::Init(command) => init::exec(command), Forc::ParseBytecode(command) => parse_bytecode::exec(command), diff --git a/forc/src/ops/forc_explorer.rs b/forc/src/ops/forc_explorer.rs new file mode 100644 index 00000000000..a2798ddf3f5 --- /dev/null +++ b/forc/src/ops/forc_explorer.rs @@ -0,0 +1,191 @@ +use std::fs::{create_dir_all, remove_dir_all, remove_file, rename, File}; +use std::io::Cursor; +use std::path::PathBuf; + +use ansi_term::Colour; +use dirs; +use reqwest; +use serde::Deserialize; +use tar::Archive; +use warp::Filter; + +use crate::cli::ExplorerCommand; +type DownloadResult = std::result::Result>; + +#[derive(Deserialize, Debug)] +struct GitHubRelease { + url: String, + assets: Vec, + name: String, +} + +#[derive(Deserialize, Debug)] +struct GitHubReleaseAsset { + browser_download_url: String, +} + +const REPO_RELEASES_URL: &str = "https://api.github.com/repos/FuelLabs/block-explorer-v2/releases"; + +struct EndPoints {} + +impl EndPoints { + pub fn static_files() -> String { + "static".to_string() + } +} + +struct ExplorerAppPaths {} + +impl ExplorerAppPaths { + pub fn web_app_path() -> PathBuf { + dirs::home_dir().unwrap().join(".fuel/explorer") + } + pub fn web_app_version_path(version: &str) -> PathBuf { + dirs::home_dir() + .unwrap() + .join(".fuel/explorer") + .join(version) + } + pub fn web_app_files_path(version: &str) -> PathBuf { + dirs::home_dir() + .unwrap() + .join(format!(".fuel/explorer/{}/www", version)) + } + pub fn build_archive_path(version: &str) -> PathBuf { + dirs::home_dir() + .unwrap() + .join(format!(".fuel/explorer/{}/build.tar", version)) + } + pub fn build_archive_unpack_path(version: &str) -> PathBuf { + dirs::home_dir() + .unwrap() + .join(format!(".fuel/explorer/{version}/build")) + } + pub fn web_app_static_assets_path(version: &str) -> PathBuf { + dirs::home_dir() + .unwrap() + .join(format!(".fuel/explorer/{}/www/static", version)) + } +} + +pub(crate) async fn exec(command: ExplorerCommand) -> Result<(), reqwest::Error> { + if command.clean.is_some() { + exec_clean().await + } else { + exec_start(command).await + } +} + +async fn exec_start(command: ExplorerCommand) -> Result<(), reqwest::Error> { + let ExplorerCommand { port, .. } = command; + let releases = get_github_releases().await?; + let version = get_latest_release_name(releases.as_slice()); + let message = format!("Fuel Network Explorer {}", version); + println!("{}", Colour::Green.paint(message)); + let is_downloaded = check_version_path(version); + + if !is_downloaded { + let url = get_release_url(releases.as_slice(), version); + match download_build(url, version).await { + Ok(arch) => arch, + Err(error) => panic!("Failed to download build {:?}", error), + }; + match unpack_archive(version) { + Ok(_) => (), + Err(error) => panic!("Failed to unpack build archive {:?}", error), + }; + if let Err(error) = rename( + ExplorerAppPaths::build_archive_unpack_path(version), + ExplorerAppPaths::web_app_files_path(version), + ) { + panic!("Failed to move static files {:?}", error) + } + match remove_file(ExplorerAppPaths::build_archive_path(version)) { + Ok(_) => (), + Err(error) => eprintln!("Failed clean up files {:?}", error), + } + } + start_server(port.as_str(), version).await; + Ok(()) +} + +async fn exec_clean() -> Result<(), reqwest::Error> { + let path = ExplorerAppPaths::web_app_path(); + if path.exists() { + match remove_dir_all(path) { + Ok(_) => (), + Err(error) => eprintln!("Failed clean up files {:?}", error), + } + } + Ok(()) +} + +async fn get_github_releases() -> Result, reqwest::Error> { + let client = reqwest::Client::new(); + let response = client + .get(REPO_RELEASES_URL) + .header("User-Agent", "warp") + .send() + .await?; + Ok(response.json().await?) +} + +fn get_latest_release_name(releases: &[GitHubRelease]) -> &str { + let a = match releases.first() { + Some(release) => release, + None => panic!("No version has been released yet!"), + }; + a.name.as_str() +} + +fn check_version_path(version: &str) -> bool { + let path = ExplorerAppPaths::web_app_version_path(version); + path.exists() +} + +fn get_release_url<'a>(releases: &'a [GitHubRelease], name: &str) -> &'a str { + let mut url: &'a str = ""; + + for release in releases { + if release.name == name { + url = &release.assets.first().unwrap().browser_download_url; + break; + } + } + url +} + +async fn download_build(url: &str, version: &str) -> DownloadResult { + create_dir_all(ExplorerAppPaths::web_app_path().join(version))?; + let mut file = match File::create(ExplorerAppPaths::build_archive_path(version)) { + Ok(fc) => fc, + Err(error) => panic!("Problem creating the build archive: {:?}", error), + }; + let response = reqwest::get(url).await?; + let mut content = Cursor::new(response.bytes().await?); + std::io::copy(&mut content, &mut file)?; + Ok(file) +} + +fn unpack_archive(version: &str) -> Result<(), std::io::Error> { + let mut ar = Archive::new(File::open(ExplorerAppPaths::build_archive_path(version)).unwrap()); + ar.unpack(ExplorerAppPaths::web_app_version_path(version)) + .unwrap(); + Ok(()) +} + +async fn start_server(port: &str, version: &str) { + let explorer = + warp::path::end().and(warp::fs::dir(ExplorerAppPaths::web_app_files_path(version))); + let static_assets = warp::path(EndPoints::static_files()).and(warp::fs::dir( + ExplorerAppPaths::web_app_static_assets_path(version), + )); + let routes = static_assets.or(explorer); + + let port_number = match port.parse::() { + Ok(n) => n, + Err(error) => panic!("Invalid port number {:?}", error), + }; + println!("Started server on 127.0.0.1:{}", port_number); + warp::serve(routes).run(([127, 0, 0, 1], port_number)).await +} diff --git a/forc/src/ops/mod.rs b/forc/src/ops/mod.rs index 86bc785834d..68ddaf27cb7 100644 --- a/forc/src/ops/mod.rs +++ b/forc/src/ops/mod.rs @@ -2,6 +2,7 @@ pub mod forc_abi_json; pub mod forc_build; pub mod forc_dep_check; pub mod forc_deploy; +pub mod forc_explorer; pub mod forc_fmt; pub mod forc_init; pub mod forc_run;