From c4fc0b2aa5ade0d776a107b44a118020ab83d977 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Thu, 18 Apr 2024 20:30:22 +0800 Subject: [PATCH] feat: use `make install-slim` to speed up (ci) build (#4218) --- .github/workflows/forest.yml | 5 +- Cargo.lock | 32 +- Cargo.toml | 35 +- Makefile | 21 +- src/cli/subcommands/attach_cmd.rs | 745 +++++++++++++++--------------- src/cli_shared/logger/mod.rs | 154 +++--- src/daemon/main.rs | 7 +- 7 files changed, 541 insertions(+), 458 deletions(-) diff --git a/.github/workflows/forest.yml b/.github/workflows/forest.yml index 76f0381d5010..99d108debfed 100644 --- a/.github/workflows/forest.yml +++ b/.github/workflows/forest.yml @@ -72,7 +72,10 @@ jobs: run: | sudo make install-deps - name: Cargo Install - run: make install + env: + # To minimize compile times: https://nnethercote.github.io/perf-book/build-configuration.html#minimizing-compile-times + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=lld" + run: make install-slim-quick - uses: actions/upload-artifact@v4 with: name: 'forest-${{ runner.os }}' diff --git a/Cargo.lock b/Cargo.lock index bbeeb9be5a50..18fc544018d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -661,7 +661,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.0", + "hyper 1.3.1", "hyper-util", "itoa", "matchit", @@ -1478,9 +1478,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" dependencies = [ "error-code", ] @@ -4472,9 +4472,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f24ce812868d86d19daa79bf3bf9175bc44ea323391147a5e3abde2a283871b" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -4514,7 +4514,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.0", + "hyper 1.3.1", "hyper-util", "rustls 0.22.3", "rustls-pki-types", @@ -4546,7 +4546,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.3.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.6", "tokio", @@ -7209,9 +7209,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -7803,7 +7803,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.0", + "hyper 1.3.1", "hyper-rustls 0.26.0", "hyper-util", "ipnet", @@ -8306,9 +8306,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -8333,9 +8333,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -8367,9 +8367,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index a129f3fde838..93f4bd65053b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,6 @@ bls-signatures = { version = "0.15", default-features = false, features = [ "blst-portable", ] } # prevent SIGINT on CI runners by using portable assembly blstrs = { version = "0.7", features = ["portable"] } -boa_engine = "0.18" -boa_interner = "0.18" -boa_parser = "0.18" -boa_runtime = "0.18" byteorder = "1.5.0" bytes = "1.2" cbor4ii = { version = "0.2.14", default-features = false, features = ["use_alloc", "use_std"] } @@ -40,8 +36,6 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] } cid = { version = "0.10", default-features = false, features = ["std"] } clap = { version = "4.5", features = ["derive"] } colored = "2.0" -# memory leak, see https://github.com/tokio-rs/console/pull/501 -console-subscriber = { version = "0.2", features = ["parking_lot"] } convert_case = "0.6.0" crypto_secretbox = "0.1.1" daemonize-me = "2.0" @@ -130,7 +124,6 @@ libsecp256k1 = "0.7" lru = "0.12" memmap2 = "0.9" memory-stats = "1.1" -mimalloc = { version = "0.1.39", optional = true, default-features = false } multiaddr = "0.18" multimap = "0.10.0" nom = "7.1.3" @@ -191,7 +184,6 @@ tar = "0.4" tempfile = "3.10" thiserror = "1.0" ticker = "0.1" -tikv-jemallocator = { version = "0.5", optional = true } tokio = { version = "1", features = ['full'] } tokio-stream = { version = "0.1", features = ["fs", "io-util"] } tokio-util = { version = "0.7.9", features = ["compat", "io-util"] } @@ -199,8 +191,6 @@ toml = "0.8" tower = { version = "0.4", features = ["full"] } tracing = "0.1" tracing-appender = "0.2" -tracing-chrome = "0.7" -tracing-loki = { version = "0.2", default-features = false, features = ["compat-0-2-1", "rustls"] } tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } unsigned-varint = { version = "0.8", features = ["codec"] } url = { version = "2.3", features = ["serde"] } @@ -208,6 +198,18 @@ uuid = { version = "1.7", features = ["v4", "serde"] } walkdir = "2" zstd = "0.13" +# Optional deps +boa_engine = { version = "0.18", optional = true } +boa_interner = { version = "0.18", optional = true } +boa_parser = { version = "0.18", optional = true } +boa_runtime = { version = "0.18", optional = true } +## memory leak, see https://github.com/tokio-rs/console/pull/501 +console-subscriber = { version = "0.2", features = ["parking_lot"], optional = true } +mimalloc = { version = "0.1.39", optional = true, default-features = false } +tikv-jemallocator = { version = "0.5", optional = true } +tracing-chrome = { version = "0.7", optional = true } +tracing-loki = { version = "0.2", default-features = false, features = ["compat-0-2-1", "rustls"], optional = true } + [target.'cfg(unix)'.dependencies] termios = "0.3" @@ -258,15 +260,22 @@ overflow-checks = true # These should be refactored (probably removed) in #2984 [features] -default = ["jemalloc"] -doctest-private = [] # see lib.rs::doctest_private -benchmark-private = [] # see lib.rs::benchmark_private +default = ["jemalloc", "tokio-console", "tracing-loki", "tracing-chrome", "attach"] +slim = ["rustalloc", "attach"] +doctest-private = [] # see lib.rs::doctest_private +benchmark-private = [] # see lib.rs::benchmark_private # Allocator rustalloc = [] jemalloc = ["dep:tikv-jemallocator"] mimalloc = ["dep:mimalloc"] +tokio-console = ["dep:console-subscriber"] +tracing-loki = ["dep:tracing-loki"] +tracing-chrome = ["dep:tracing-chrome"] + +attach = ["dep:boa_engine", "dep:boa_interner", "dep:boa_parser", "dep:boa_runtime"] + [[bench]] name = "example-benchmark" harness = false diff --git a/Makefile b/Makefile index 7f74baa3cab6..52ef481ba56f 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,22 @@ install-daemon: install: cargo install --locked --path . --force + +install-quick: + cargo install --profile quick --locked --path . --force + +install-slim: + cargo install --no-default-features --features slim --locked --path . --force + +install-slim-quick: + cargo install --profile quick --no-default-features --features slim --locked --path . --force + +install-minimum: + cargo install --no-default-features --locked --path . --force + +install-minimum-quick: + cargo install --profile quick --no-default-features --locked --path . --force + # Installs Forest binaries with default rust global allocator install-with-rustalloc: cargo install --locked --path . --force --no-default-features --features rustalloc @@ -66,8 +82,9 @@ lint: license clean lint-clippy # This should be simplified in #2984 # --quiet: don't show build logs lint-clippy: - cargo clippy --quiet --no-deps -- --deny=warnings - cargo clippy --tests --quiet --no-deps -- --deny=warnings + cargo clippy --all-targets --quiet --no-deps -- --deny=warnings + cargo clippy --all-targets --no-default-features --features slim --quiet --no-deps -- --deny=warnings + cargo clippy --all-targets --no-default-features --quiet --no-deps -- --deny=warnings cargo clippy --benches --features benchmark-private --quiet --no-deps -- --deny=warnings DOCKERFILES=$(wildcard Dockerfile*) diff --git a/src/cli/subcommands/attach_cmd.rs b/src/cli/subcommands/attach_cmd.rs index 36d568f853f4..0a6dd38c09a9 100644 --- a/src/cli/subcommands/attach_cmd.rs +++ b/src/cli/subcommands/attach_cmd.rs @@ -1,36 +1,8 @@ // Copyright 2019-2024 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use std::{ - fs::{canonicalize, read_to_string, OpenOptions}, - path::PathBuf, - str::FromStr, -}; - -use crate::chain_sync::SyncStage; -use crate::rpc_client::*; -use crate::shim::{address::Address, message::Message}; -use crate::{ - chain::ChainEpochDelta, - rpc::{self, prelude::*}, -}; -use crate::{cli::humantoken, message::SignedMessage}; -use boa_engine::{ - object::{builtins::JsArray, FunctionObjectBuilder}, - prelude::JsObject, - property::Attribute, - Context, JsError, JsResult, JsString, JsValue, NativeFunction, Source, -}; -use boa_interner::Interner; -use boa_parser::Parser; -use boa_runtime::Console; -use convert_case::{Case, Casing}; -use directories::BaseDirs; -use futures::Future; -use rustyline::{config::Config as RustyLineConfig, history::FileHistory, EditMode, Editor}; -use serde::{de::DeserializeOwned, Serialize}; -use serde_json::Value as JsonValue; -use tokio::time; +use crate::rpc_client::ApiInfo; +use std::path::PathBuf; #[derive(Debug, clap::Args)] pub struct AttachCommand { @@ -43,297 +15,346 @@ pub struct AttachCommand { exec: Option, } -const PRELUDE_PATH: &str = include_str!("./js/prelude.js"); - -fn set_module(context: &mut Context) { - let module = JsObject::default(); - module - .set( - JsString::from("exports"), - JsObject::default(), - false, - context, - ) - .unwrap(); - context - .register_global_property( - JsString::from("module"), - JsValue::from(module), - Attribute::default(), - ) - .expect("`register_global_property` should not fail"); -} - -fn to_position(err: boa_parser::Error) -> Option<(u32, u32)> { - use boa_parser::Error::*; - - match err { - Expected { - expected: _, - found: _, - span, - context: _, - } => Some((span.start().line_number(), span.start().column_number())), - Unexpected { - found: _, - span, - message: _, - } => Some((span.start().line_number(), span.start().column_number())), - General { - message: _, - position, - } => Some((position.line_number(), position.column_number())), - Lex { err: _ } | AbruptEnd => None, +impl AttachCommand { + #[cfg(not(feature = "attach"))] + pub fn run(self, _api: ApiInfo) -> anyhow::Result<()> { + tracing::warn!("`attach` command is unavailable, forest binaries need to be recompiled with `attach` feature"); + Ok(()) } -} -fn eval(code: &str, context: &mut Context) { - match context.eval(Source::from_bytes(code)) { - Ok(v) => match v { - JsValue::Undefined => (), - _ => println!("{}", v.display()), - }, - Err(err) => { - eprintln!("Uncaught {err}"); - } + #[cfg(feature = "attach")] + pub fn run(self, api: ApiInfo) -> anyhow::Result<()> { + self.run_inner(api) } } -fn require( - _: &JsValue, - params: &[JsValue], - context: &mut Context, - jspath: &Option, -) -> JsResult { - let param = if let Some(p) = params.first() { - p - } else { - return Err(JsError::from_opaque( - JsString::from("expecting string argument").into(), - )); +#[cfg(feature = "attach")] +mod inner { + use std::{ + fs::{canonicalize, read_to_string, OpenOptions}, + str::FromStr, }; - // Resolve module path - let module_name = param.to_string(context)?.to_std_string_escaped(); - let mut path = if let Some(path) = jspath { - path.join(module_name) - } else { - PathBuf::from(module_name) + use super::*; + use crate::chain_sync::SyncStage; + use crate::rpc_client::*; + use crate::shim::{address::Address, message::Message}; + use crate::{ + chain::ChainEpochDelta, + rpc::{self, prelude::*}, }; - // Check if path does not exist and append .js if file has no extension - if !path.exists() && path.extension().is_none() { - path.set_extension("js"); - } - let result = if path.exists() { - read_to_string(path.clone()) - } else if path == PathBuf::from("prelude.js") { - Ok(PRELUDE_PATH.into()) - } else { - return Err(JsError::from_opaque( - JsString::from("expecting valid module path").into(), - )); + use crate::{cli::humantoken, message::SignedMessage}; + use boa_engine::{ + object::{builtins::JsArray, FunctionObjectBuilder}, + prelude::JsObject, + property::Attribute, + Context, JsError, JsResult, JsString, JsValue, NativeFunction, Source, }; - match result { - Ok(buffer) => { - let mut parser = Parser::new(Source::from_bytes(&buffer)); - let mut interner = Interner::new(); - if let Err(err) = parser.parse_eval(true, &mut interner) { - let canonical_path = canonicalize(path.clone()).unwrap_or(path.clone()); - eprintln!("{}", canonical_path.display()); - - if let Some((line, column)) = to_position(err) { - // Display a few lines for context - const MAX_WINDOW: usize = 3; - let start_index = 0.max(line as isize - MAX_WINDOW as isize) as usize; - let window_len = line as usize - start_index; - for l in buffer.split('\n').skip(start_index).take(window_len) { - println!("{l}"); - } - // Column is always strictly superior to zero - println!("{}^", " ".to_owned().repeat(column as usize - 1)); - } - println!(); - } - context.eval(Source::from_bytes(&buffer))?; - - // Access module.exports and return as ResultValue - let global_obj = context.global_object().to_owned(); - let module = global_obj - .get(JsString::from("module"), context) - .expect("get must succeed"); - let exports = module - .as_object() - .expect("as_object must succeed") - .get(JsString::from("exports"), context); - - exports + use boa_interner::Interner; + use boa_parser::Parser; + use boa_runtime::Console; + use convert_case::{Case, Casing}; + use directories::BaseDirs; + use futures::Future; + use rustyline::{config::Config as RustyLineConfig, history::FileHistory, EditMode, Editor}; + use serde::{de::DeserializeOwned, Serialize}; + use serde_json::Value as JsonValue; + use tokio::time; + + const PRELUDE_PATH: &str = include_str!("./js/prelude.js"); + + fn set_module(context: &mut Context) { + let module = JsObject::default(); + module + .set( + JsString::from("exports"), + JsObject::default(), + false, + context, + ) + .unwrap(); + context + .register_global_property( + JsString::from("module"), + JsValue::from(module), + Attribute::default(), + ) + .expect("`register_global_property` should not fail"); + } + + fn to_position(err: boa_parser::Error) -> Option<(u32, u32)> { + use boa_parser::Error::*; + + match err { + Expected { + expected: _, + found: _, + span, + context: _, + } => Some((span.start().line_number(), span.start().column_number())), + Unexpected { + found: _, + span, + message: _, + } => Some((span.start().line_number(), span.start().column_number())), + General { + message: _, + position, + } => Some((position.line_number(), position.column_number())), + Lex { err: _ } | AbruptEnd => None, } - Err(err) => { - eprintln!("Error: {err}"); - Ok(JsValue::Undefined) + } + + fn eval(code: &str, context: &mut Context) { + match context.eval(Source::from_bytes(code)) { + Ok(v) => match v { + JsValue::Undefined => (), + _ => println!("{}", v.display()), + }, + Err(err) => { + eprintln!("Uncaught {err}"); + } } } -} -fn check_result(context: &mut Context, result: anyhow::Result) -> JsResult -where - R: Serialize, -{ - match result { - Ok(v) => { - let value: JsonValue = serde_json::to_value(v) - .map_err(|e| JsError::from_opaque(JsString::from(e.to_string()).into()))?; - JsValue::from_json(&value, context) + fn require( + _: &JsValue, + params: &[JsValue], + context: &mut Context, + jspath: &Option, + ) -> JsResult { + let param = if let Some(p) = params.first() { + p + } else { + return Err(JsError::from_opaque( + JsString::from("expecting string argument").into(), + )); + }; + + // Resolve module path + let module_name = param.to_string(context)?.to_std_string_escaped(); + let mut path = if let Some(path) = jspath { + path.join(module_name) + } else { + PathBuf::from(module_name) + }; + // Check if path does not exist and append .js if file has no extension + if !path.exists() && path.extension().is_none() { + path.set_extension("js"); } - Err(err) => { - eprintln!("Error: {err}"); - Ok(JsValue::Undefined) + let result = if path.exists() { + read_to_string(path.clone()) + } else if path == PathBuf::from("prelude.js") { + Ok(PRELUDE_PATH.into()) + } else { + return Err(JsError::from_opaque( + JsString::from("expecting valid module path").into(), + )); + }; + match result { + Ok(buffer) => { + let mut parser = Parser::new(Source::from_bytes(&buffer)); + let mut interner = Interner::new(); + if let Err(err) = parser.parse_eval(true, &mut interner) { + let canonical_path = canonicalize(path.clone()).unwrap_or(path.clone()); + eprintln!("{}", canonical_path.display()); + + if let Some((line, column)) = to_position(err) { + // Display a few lines for context + const MAX_WINDOW: usize = 3; + let start_index = 0.max(line as isize - MAX_WINDOW as isize) as usize; + let window_len = line as usize - start_index; + for l in buffer.split('\n').skip(start_index).take(window_len) { + println!("{l}"); + } + // Column is always strictly superior to zero + println!("{}^", " ".to_owned().repeat(column as usize - 1)); + } + println!(); + } + context.eval(Source::from_bytes(&buffer))?; + + // Access module.exports and return as ResultValue + let global_obj = context.global_object().to_owned(); + let module = global_obj + .get(JsString::from("module"), context) + .expect("get must succeed"); + let exports = module + .as_object() + .expect("as_object must succeed") + .get(JsString::from("exports"), context); + + exports + } + Err(err) => { + eprintln!("Error: {err}"); + Ok(JsValue::Undefined) + } } } -} -fn bind_async( - context: &mut Context, - api: &ApiInfo, - name: &'static str, - req: impl Fn(T, ApiInfo) -> Fut + 'static, -) where - Fut: Future>, -{ - let js_func_name = name.to_case(Case::Camel); - // Safety: This is unsafe since GC'ed variables caught in the closure will - // not get traced. We're safe because we do not use any GC'ed variables. - let js_func = FunctionObjectBuilder::new(context.realm(), unsafe { - NativeFunction::from_closure({ - let api = api.clone(); - move |_this, params, context| { - let handle = tokio::runtime::Handle::current(); - - let result = tokio::task::block_in_place(|| { - let value = if params.is_empty() { - JsValue::Null - } else { - let arr = JsArray::from_iter(params.to_vec(), context); - let obj: JsObject = arr.into(); - JsValue::from(obj) - }; - let args = serde_json::from_value( - value.to_json(context).map_err(|e| anyhow::anyhow!("{e}"))?, - )?; - handle.block_on(req(args, api.clone())) - }); - check_result(context, result) + fn check_result(context: &mut Context, result: anyhow::Result) -> JsResult + where + R: Serialize, + { + match result { + Ok(v) => { + let value: JsonValue = serde_json::to_value(v) + .map_err(|e| JsError::from_opaque(JsString::from(e.to_string()).into()))?; + JsValue::from_json(&value, context) + } + Err(err) => { + eprintln!("Error: {err}"); + Ok(JsValue::Undefined) } + } + } + + fn bind_async( + context: &mut Context, + api: &ApiInfo, + name: &'static str, + req: impl Fn(T, ApiInfo) -> Fut + 'static, + ) where + Fut: Future>, + { + let js_func_name = name.to_case(Case::Camel); + // Safety: This is unsafe since GC'ed variables caught in the closure will + // not get traced. We're safe because we do not use any GC'ed variables. + let js_func = FunctionObjectBuilder::new(context.realm(), unsafe { + NativeFunction::from_closure({ + let api = api.clone(); + move |_this, params, context| { + let handle = tokio::runtime::Handle::current(); + + let result = tokio::task::block_in_place(|| { + let value = if params.is_empty() { + JsValue::Null + } else { + let arr = JsArray::from_iter(params.to_vec(), context); + let obj: JsObject = arr.into(); + JsValue::from(obj) + }; + let args = serde_json::from_value( + value.to_json(context).map_err(|e| anyhow::anyhow!("{e}"))?, + )?; + handle.block_on(req(args, api.clone())) + }); + check_result(context, result) + } + }) }) - }) - .name(js_func_name.clone()) - .build(); - - let attr = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - context - .register_global_property(JsString::from(js_func_name), js_func, attr) - .expect("`register_global_property` should not fail"); -} + .name(js_func_name.clone()) + .build(); + + let attr = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; + context + .register_global_property(JsString::from(js_func_name), js_func, attr) + .expect("`register_global_property` should not fail"); + } -macro_rules! bind_request { + macro_rules! bind_request { ($context:expr, $api:expr, $($name:literal => $req:expr),* $(,)?) => { $( bind_async($context, &$api, $name, move |args, api| { // Some of the closures are redundant, others are not. #[allow(clippy::redundant_closure_call)] - let rpc = $req(args).lower(); + let rpc: crate::rpc_client::RpcRequest = $req(args).map_ty(); async move { Ok(api.call(rpc).await?) } }); )* }; } -type SendMessageParams = (String, String, String); - -async fn send_message(params: SendMessageParams, api: ApiInfo) -> anyhow::Result { - let (from, to, value) = params; - - let message = Message::transfer( - Address::from_str(&from)?, - Address::from_str(&to)?, - humantoken::parse(&value)?, // Convert forest_shim::TokenAmount to TokenAmount3 - ); - Ok( - MpoolPushMessage::call(&rpc::Client::from(api), (message.into(), None)) - .await? - .into_inner(), - ) -} + type SendMessageParams = (String, String, String); + + async fn send_message( + params: SendMessageParams, + api: ApiInfo, + ) -> anyhow::Result { + let (from, to, value) = params; + + let message = Message::transfer( + Address::from_str(&from)?, + Address::from_str(&to)?, + humantoken::parse(&value)?, // Convert forest_shim::TokenAmount to TokenAmount3 + ); + Ok( + MpoolPushMessage::call(&rpc::Client::from(api), (message.into(), None)) + .await? + .into_inner(), + ) + } -type SleepParams = (u64,); -type SleepResult = (); + type SleepParams = (u64,); + type SleepResult = (); -async fn sleep(params: SleepParams, _api: ApiInfo) -> anyhow::Result { - let secs = params.0; - time::sleep(time::Duration::from_secs(secs)).await; - Ok(()) -} + async fn sleep(params: SleepParams, _api: ApiInfo) -> anyhow::Result { + let secs = params.0; + time::sleep(time::Duration::from_secs(secs)).await; + Ok(()) + } -async fn sleep_tipsets(epochs: ChainEpochDelta, api: ApiInfo) -> anyhow::Result<()> { - let client = rpc::Client::from(api); - let mut epoch = None; - loop { - let state = SyncState::call(&client, ()).await?; - if state.active_syncs.as_ref().first().stage() == SyncStage::Complete { - if let Some(prev) = epoch { - let curr = state.active_syncs.as_ref().first().epoch(); - if (curr - prev) >= epochs { - return Ok(()); + async fn sleep_tipsets(epochs: ChainEpochDelta, api: ApiInfo) -> anyhow::Result<()> { + let client = rpc::Client::from(api); + let mut epoch = None; + loop { + let state = SyncState::call(&client, ()).await?; + if state.active_syncs.as_ref().first().stage() == SyncStage::Complete { + if let Some(prev) = epoch { + let curr = state.active_syncs.as_ref().first().epoch(); + if (curr - prev) >= epochs { + return Ok(()); + } + } else { + epoch = Some(state.active_syncs.as_ref().first().epoch()); } - } else { - epoch = Some(state.active_syncs.as_ref().first().epoch()); } + time::sleep(time::Duration::from_secs(1)).await; } - time::sleep(time::Duration::from_secs(1)).await; } -} - -impl AttachCommand { - fn setup_context(&self, context: &mut Context, api: ApiInfo) { - let console = Console::init(context); - context - .register_global_property(JsString::from(Console::NAME), console, Attribute::all()) - .expect("the console object shouldn't exist yet"); - context - .register_global_property( - JsString::from("_BOA_VERSION"), - JsString::from("0.18.0"), - Attribute::default(), - ) - .expect("`register_global_property` should not fail"); - - // Safety: This is unsafe since GC'ed variables caught in the closure will - // not get traced. We're safe because we do not use any GC'ed variables. - // Add custom implementation that mimics `require` - let require_func = unsafe { - NativeFunction::from_closure_with_captures( - |_this, params, jspath, context| require(_this, params, context, jspath), - self.jspath.clone(), - ) - }; - - context - .register_global_builtin_callable(JsString::from("require"), 1, require_func) - .expect("Registering the global`require` should succeed"); - - // Add custom object that mimics `module.exports` - set_module(context); - bind_request!(context, api,); - - // Bind send_message, sleep, sleep_tipsets - bind_async(context, &api, "send_message", send_message); - bind_async(context, &api, "seep", sleep); - bind_async(context, &api, "sleep_tipsets", sleep_tipsets); - } + impl AttachCommand { + fn setup_context(&self, context: &mut Context, api: ApiInfo) { + let console = Console::init(context); + context + .register_global_property(JsString::from(Console::NAME), console, Attribute::all()) + .expect("the console object shouldn't exist yet"); + context + .register_global_property( + JsString::from("_BOA_VERSION"), + JsString::from("0.18.0"), + Attribute::default(), + ) + .expect("`register_global_property` should not fail"); + + // Safety: This is unsafe since GC'ed variables caught in the closure will + // not get traced. We're safe because we do not use any GC'ed variables. + // Add custom implementation that mimics `require` + let require_func = unsafe { + NativeFunction::from_closure_with_captures( + |_this, params, jspath, context| require(_this, params, context, jspath), + self.jspath.clone(), + ) + }; + + context + .register_global_builtin_callable(JsString::from("require"), 1, require_func) + .expect("Registering the global`require` should succeed"); + + // Add custom object that mimics `module.exports` + set_module(context); + + bind_request!(context, api,); + + // Bind send_message, sleep, sleep_tipsets + bind_async(context, &api, "send_message", send_message); + bind_async(context, &api, "seep", sleep); + bind_async(context, &api, "sleep_tipsets", sleep_tipsets); + } - fn import_prelude(&self, context: &mut Context) -> anyhow::Result<()> { - const INIT: &str = r" + fn import_prelude(&self, context: &mut Context) -> anyhow::Result<()> { + const INIT: &str = r" const Prelude = require('prelude.js') if (Prelude.showPeers) { showPeers = Prelude.showPeers; } if (Prelude.getPeer) { getPeer = Prelude.getPeer; } @@ -344,106 +365,108 @@ impl AttachCommand { if (Prelude.sendFIL) { sendFIL = Prelude.sendFIL; } "; - if let Err(err) = context.eval(Source::from_bytes(INIT)) { - return Err(anyhow::anyhow!("error {err:?}")); - } - - Ok(()) - } + if let Err(err) = context.eval(Source::from_bytes(INIT)) { + return Err(anyhow::anyhow!("error {err:?}")); + } - pub fn run(self, api: ApiInfo) -> anyhow::Result<()> { - let mut context = Context::default(); - self.setup_context(&mut context, api); + Ok(()) + } - self.import_prelude(&mut context)?; + pub(super) fn run_inner(self, api: ApiInfo) -> anyhow::Result<()> { + let mut context = Context::default(); + self.setup_context(&mut context, api); - // If only a short execution was requested, evaluate and return - if let Some(code) = &self.exec { - eval(code.trim_end(), &mut context); - return Ok(()); - } + self.import_prelude(&mut context)?; - eval("Prelude.greet()", &mut context); + // If only a short execution was requested, evaluate and return + if let Some(code) = &self.exec { + eval(code.trim_end(), &mut context); + return Ok(()); + } - let config = RustyLineConfig::builder() - .keyseq_timeout(Some(1)) - .edit_mode(EditMode::Emacs) - .build(); + eval("Prelude.greet()", &mut context); - let mut editor: Editor<(), FileHistory> = Editor::with_config(config)?; + let config = RustyLineConfig::builder() + .keyseq_timeout(Some(1)) + .edit_mode(EditMode::Emacs) + .build(); - let history_path = if let Some(dirs) = BaseDirs::new() { - let path = dirs.home_dir().join(".forest_history"); + let mut editor: Editor<(), FileHistory> = Editor::with_config(config)?; - // Check if the history file exists. If it does not, create it. - OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(false) - .open(&path)?; + let history_path = if let Some(dirs) = BaseDirs::new() { + let path = dirs.home_dir().join(".forest_history"); - // This is safe to call at this point - editor.load_history(&path).unwrap(); + // Check if the history file exists. If it does not, create it. + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(false) + .open(&path)?; - Some(path) - } else { - None - }; + // This is safe to call at this point + editor.load_history(&path).unwrap(); - 'main: loop { - let mut prompt = "> "; - let mut buffer = String::new(); - loop { - match editor.readline(prompt) { - Ok(input) => { - if input == ":quit" { - break 'main; - } - if input == ":clear" { - editor.clear_history()?; - break; - } - if buffer.is_empty() && input.is_empty() { - // No-op - continue 'main; + Some(path) + } else { + None + }; + + 'main: loop { + let mut prompt = "> "; + let mut buffer = String::new(); + loop { + match editor.readline(prompt) { + Ok(input) => { + if input == ":quit" { + break 'main; + } + if input == ":clear" { + editor.clear_history()?; + break; + } + if buffer.is_empty() && input.is_empty() { + // No-op + continue 'main; + } + buffer.push_str(&input) } - buffer.push_str(&input) + Err(_) => break 'main, } - Err(_) => break 'main, - } - let mut parser = Parser::new(Source::from_bytes(&buffer)); - let mut interner = Interner::new(); - match parser.parse_eval(true, &mut interner) { - Ok(_) => { - editor.add_history_entry(&buffer)?; - eval(buffer.trim_end(), &mut context); - break; - } - Err(err) => { - match err { - boa_parser::Error::Lex { err: _ } | boa_parser::Error::AbruptEnd => { - // Continue reading input and append it to buffer - buffer.push('\n'); - prompt = ">> "; - } - _ => { - eprintln!("Uncaught ParseError: {err}"); - break; + let mut parser = Parser::new(Source::from_bytes(&buffer)); + let mut interner = Interner::new(); + match parser.parse_eval(true, &mut interner) { + Ok(_) => { + editor.add_history_entry(&buffer)?; + eval(buffer.trim_end(), &mut context); + break; + } + Err(err) => { + match err { + boa_parser::Error::Lex { err: _ } + | boa_parser::Error::AbruptEnd => { + // Continue reading input and append it to buffer + buffer.push('\n'); + prompt = ">> "; + } + _ => { + eprintln!("Uncaught ParseError: {err}"); + break; + } } } } } } - } - if let Some(path) = history_path { - editor - .save_history(&path) - .expect("save_history should work"); - } + if let Some(path) = history_path { + editor + .save_history(&path) + .expect("save_history should work"); + } - Ok(()) + Ok(()) + } } } diff --git a/src/cli_shared/logger/mod.rs b/src/cli_shared/logger/mod.rs index 1605867b0fb5..2c2373ebcdb0 100644 --- a/src/cli_shared/logger/mod.rs +++ b/src/cli_shared/logger/mod.rs @@ -1,81 +1,111 @@ // Copyright 2019-2024 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use tracing_chrome::{ChromeLayerBuilder, FlushGuard}; -use tracing_subscriber::{filter::LevelFilter, prelude::*, EnvFilter}; +use std::pin::Pin; + +use futures::Future; +use tracing_subscriber::{prelude::*, EnvFilter, Registry}; use crate::cli_shared::cli::CliOpts; use crate::utils::misc::LoggingColor; -pub fn setup_logger(opts: &CliOpts) -> (Option, Option) { - let mut loki_task = None; - let tracing_tokio_console = if opts.tokio_console { - Some( - console_subscriber::ConsoleLayer::builder() - .with_default_env() - .spawn(), - ) - } else { - None - }; - let tracing_loki = if opts.loki { - let (layer, task) = tracing_loki::layer( - tracing_loki::url::Url::parse(&opts.loki_endpoint) - .map_err(|e| format!("Unable to parse loki endpoint {}: {e}", &opts.loki_endpoint)) - .unwrap(), - vec![( - "host".into(), - gethostname::gethostname() - .to_str() - .unwrap_or_default() - .into(), - )] - .into_iter() - .collect(), - Default::default(), - ) - .map_err(|e| format!("Unable to create loki layer: {e}")) - .unwrap(); - loki_task = Some(task); - Some(layer.with_filter(LevelFilter::DEBUG)) - } else { - None - }; - let tracing_rolling_file = if let Some(log_dir) = &opts.log_dir { +type BackgroundTask = Pin + Send>>; + +#[derive(Default)] +pub struct Guards { + #[cfg(feature = "tracing-chrome")] + tracing_chrome: Option, +} + +#[allow(unused_mut)] +pub fn setup_logger(opts: &CliOpts) -> (Vec, Guards) { + let mut background_tasks: Vec = vec![]; + let mut guards = Guards::default(); + let mut layers: Vec + Send + Sync>> = + // console logger + vec![Box::new( + tracing_subscriber::fmt::Layer::new() + .with_ansi(opts.color.coloring_enabled()) + .with_filter(get_env_filter(default_env_filter())), + )]; + + // file logger + if let Some(log_dir) = &opts.log_dir { let file_appender = tracing_appender::rolling::hourly(log_dir, "forest.log"); - Some( + layers.push(Box::new( tracing_subscriber::fmt::Layer::new() .with_ansi(false) .with_writer(file_appender) .with_filter(get_env_filter(default_env_filter())), - ) - } else { - None - }; + )); + } + + if opts.tokio_console { + #[cfg(not(feature = "tokio-console"))] + tracing::warn!("`tokio-console` is unavailable, forest binaries need to be recompiled with `tokio-console` feature"); + + #[cfg(feature = "tokio-console")] + layers.push(Box::new( + console_subscriber::ConsoleLayer::builder() + .with_default_env() + .spawn(), + )); + } + + if opts.loki { + #[cfg(not(feature = "tracing-loki"))] + tracing::warn!("`tracing-loki` is unavailable, forest binaries need to be recompiled with `tracing-loki` feature"); + + #[cfg(feature = "tracing-loki")] + { + let (layer, task) = tracing_loki::layer( + tracing_loki::url::Url::parse(&opts.loki_endpoint) + .map_err(|e| { + format!("Unable to parse loki endpoint {}: {e}", &opts.loki_endpoint) + }) + .unwrap(), + vec![( + "host".into(), + gethostname::gethostname() + .to_str() + .unwrap_or_default() + .into(), + )] + .into_iter() + .collect(), + Default::default(), + ) + .map_err(|e| format!("Unable to create loki layer: {e}")) + .unwrap(); + background_tasks.push(Box::pin(task)); + layers.push(Box::new( + layer.with_filter(tracing_subscriber::filter::LevelFilter::DEBUG), + )); + } + } // Go to to browse trace files. // You may want to call ChromeLayerBuilder::trace_style as appropriate - let (chrome_layer, flush_guard) = - match std::env::var_os("CHROME_TRACE_FILE").map(|path| match path.is_empty() { - true => ChromeLayerBuilder::new().build(), - false => ChromeLayerBuilder::new().file(path).build(), - }) { - Some((a, b)) => (Some(a), Some(b)), - None => (None, None), - }; + if let Some(_chrome_trace_file) = std::env::var_os("CHROME_TRACE_FILE") { + #[cfg(not(feature = "tracing-chrome"))] + tracing::warn!("`tracing-chrome` is unavailable, forest binaries need to be recompiled with `tracing-chrome` feature"); - tracing_subscriber::registry() - .with(tracing_tokio_console) - .with(tracing_loki) - .with(tracing_rolling_file) - .with(chrome_layer) - .with( - tracing_subscriber::fmt::Layer::new() - .with_ansi(opts.color.coloring_enabled()) - .with_filter(get_env_filter(default_env_filter())), - ) - .init(); - (loki_task, flush_guard) + #[cfg(feature = "tracing-chrome")] + { + let (layer, guard) = match _chrome_trace_file.is_empty() { + true => tracing_chrome::ChromeLayerBuilder::new().build(), + false => tracing_chrome::ChromeLayerBuilder::new() + .file(_chrome_trace_file) + .build(), + }; + + guards.tracing_chrome = Some(guard); + layers.push(Box::new(layer)); + } + } + + tracing_subscriber::registry().with(layers).init(); + (background_tasks, guards) } // Log warnings to stderr diff --git a/src/daemon/main.rs b/src/daemon/main.rs index 4030ce7ef870..dacf6d751dcd 100644 --- a/src/daemon/main.rs +++ b/src/daemon/main.rs @@ -87,7 +87,7 @@ where // Run forest as a daemon if no other subcommands are used. Otherwise, run the // subcommand. - let (loki_task, _chrome_flush_guard) = logger::setup_logger(&opts); + let (background_tasks, _guards) = logger::setup_logger(&opts); if let Some(path) = &path { match path { @@ -127,9 +127,10 @@ where .enable_all() .build()?; - if let Some(loki_task) = loki_task { - rt.spawn(loki_task); + for task in background_tasks { + rt.spawn(task); } + let ret = rt.block_on(super::start_interruptable(opts, cfg)); info!("Shutting down tokio..."); rt.shutdown_timeout(Duration::from_secs_f32(0.5));