From bb7cab8c10a3e6c5bb9ccf9a6117956166d9683a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Terenti=C4=87?= Date: Mon, 2 Sep 2024 22:46:20 +0200 Subject: [PATCH] Implement avail-light-web --- .github/workflows/default.yml | 8 +- Cargo.lock | 43 +++++++++ Cargo.toml | 3 +- core/Cargo.toml | 3 +- core/src/light_client.rs | 1 + core/src/network/p2p.rs | 5 +- core/src/utils.rs | 7 ++ web/Cargo.toml | 30 +++++++ web/README.md | 16 ++++ web/src/lib.rs | 161 ++++++++++++++++++++++++++++++++++ web/www/index.html | 18 ++++ 11 files changed, 288 insertions(+), 7 deletions(-) create mode 100644 web/Cargo.toml create mode 100644 web/README.md create mode 100644 web/src/lib.rs create mode 100644 web/www/index.html diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index cd5806edb..013f8fe17 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -33,7 +33,9 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: components: clippy - - run: cargo clippy --workspace -- -D warnings + # TODO: Enable avail-light-web once issue with rocksdb feature being applied + # accross the workspace is resolved + - run: cargo clippy --workspace --exclude avail-light-web -- -D warnings test: name: cargo test @@ -42,7 +44,9 @@ jobs: - uses: actions/checkout@v4 - uses: arduino/setup-protoc@v2 - uses: actions-rust-lang/setup-rust-toolchain@v1 - - run: cargo test --workspace --benches --tests + # TODO: Enable avail-light-web once issue with rocksdb feature being applied + # accross the workspace is resolved + - run: cargo test --workspace --benches --tests --exclude avail-light-web env: RUSTFLAGS: "-C instrument-coverage" LLVM_PROFILE_FILE: "profile-%p-%m.profraw" diff --git a/Cargo.lock b/Cargo.lock index b3573cdad..94027ea7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,6 +1106,28 @@ dependencies = [ "warp", ] +[[package]] +name = "avail-light-web" +version = "0.1.0" +dependencies = [ + "avail-light-core", + "avail-rust", + "clap", + "console_error_panic_hook", + "futures", + "libp2p", + "sp-io", + "tokio", + "tokio_with_wasm", + "tracing", + "tracing-subscriber 0.3.18", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", +] + [[package]] name = "avail-rust" version = "0.1.0" @@ -1806,6 +1828,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -9276,6 +9308,17 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber 0.3.18", + "wasm-bindgen", +] + [[package]] name = "trie-db" version = "0.28.0" diff --git a/Cargo.toml b/Cargo.toml index b949c6ca0..36d822fbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crawler", "fat", "relay", + "web", ] default-members = ["client"] resolver = "2" @@ -25,7 +26,7 @@ anyhow = "1.0.71" async-std = { version = "1.12.0", features = ["attributes"] } async-trait = "0.1.73" clap = { version = "4.4.4", features = ["derive", "cargo"] } -color-eyre = "0.6.2" +color-eyre = { version = "0.6.2", default-features = false } confy = "0.5.1" hex = "0.4.3" rand = "0.8.4" diff --git a/core/Cargo.toml b/core/Cargo.toml index 22e9703cf..4aba6a5e4 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -20,7 +20,6 @@ better-panic = "0.3.0" blake2b_simd = "1.0.2" clap = { workspace = true } codec = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "full", "bit-vec"] } -color-eyre = { workspace = true } confy = { workspace = true } derive_more = { version = "1", features = ["from"] } dusk-bytes = { version = "0.1.6", default-features = false } @@ -49,6 +48,7 @@ uuid = { workspace = true } async-std = { workspace = true } chrono = "0.4.19" libp2p = { workspace = true } +color-eyre = { workspace = true, default-features = true } hyper = { version = "0.14.23", features = ["full", "http1"] } jsonrpsee-core = { version = "0.21.0", features = ["client"] } libc = "0.2.150" @@ -76,6 +76,7 @@ thiserror-no-std = "2.0.2" rand = { version = "0.8.4", default-features = false } libp2p = { workspace = true, features = ["wasm-bindgen"] } libp2p-webrtc-websys = { workspace = true } +color-eyre = { workspace = true } wasm-bindgen = "0.2.90" wasm-timer = "0.2.5" web-time = "1.1.0" diff --git a/core/src/light_client.rs b/core/src/light_client.rs index 8477352fb..8caee33be 100644 --- a/core/src/light_client.rs +++ b/core/src/light_client.rs @@ -43,6 +43,7 @@ use crate::{ utils::{blake2_256, calculate_confidence, extract_kate}, }; +#[derive(Debug)] pub enum OutputEvent { RecordBlockProcessingDelay(f64), CountSessionBlocks, diff --git a/core/src/network/p2p.rs b/core/src/network/p2p.rs index 6bbbba971..8e09eff16 100644 --- a/core/src/network/p2p.rs +++ b/core/src/network/p2p.rs @@ -315,10 +315,9 @@ async fn build_swarm( }; #[cfg(target_arch = "wasm32")] { + use libp2p_webrtc_websys as webrtc; swarm = tokio_swarm - .with_other_transport(|key| { - libp2p_webrtc_websys::Transport::new(libp2p_webrtc_websys::Config::new(&key)) - })? + .with_other_transport(|key| webrtc::Transport::new(webrtc::Config::new(&key)))? .with_relay_client(noise::Config::new, yamux::Config::default)? .with_behaviour(behaviour)? .with_swarm_config(|c| generate_config(c, cfg)) diff --git a/core/src/utils.rs b/core/src/utils.rs index a98529bed..97c86d9c5 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -115,12 +115,19 @@ pub fn filter_auth_set_changes(header: &AvailHeader) -> Vec) -> Result<()> { + #[cfg(not(target_arch = "wasm32"))] // initialize color-eyre hooks let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default() .display_location_section(true) .display_env_section(true) .into_hooks(); + #[cfg(target_arch = "wasm32")] + // initialize color-eyre hooks + let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default() + .display_env_section(true) + .into_hooks(); + // install hook as global handler eyre_hook.install()?; diff --git a/web/Cargo.toml b/web/Cargo.toml new file mode 100644 index 000000000..32a9f7909 --- /dev/null +++ b/web/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "avail-light-web" +version = "0.1.0" +authors.workspace = true +edition = "2021" +repository.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +avail-light-core = { workspace = true } +avail-rust = { workspace = true } +clap = { workspace = true } +console_error_panic_hook = { version = "0.1.7", optional = true } +futures = { workspace = true } +libp2p = { workspace = true } +sp-io = { version = "30", features = ["disable_allocator", "disable_panic_handler"], default-features = false } +tokio = { version = "^1", default-features = false, features = ["sync", "macros", "io-util", "rt"] } +tokio_with_wasm = { version = "0.7.1", features = ["sync", "macros", "rt"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +tracing-wasm = "0.2.1" +wasm-bindgen = "0.2.93" +wasm-bindgen-futures = "0.4.43" +web-sys = { version = "0.3.70", features = ["console", "Window", "UrlSearchParams"] } +web-time = "1.1.0" diff --git a/web/README.md b/web/README.md new file mode 100644 index 000000000..c442bb09b --- /dev/null +++ b/web/README.md @@ -0,0 +1,16 @@ +# Avail Light Client (Web) + +## Compile + +`wasm-pack build --target web --dev` + +## Run + +`cp www/index.html pkg/` +`cd pkg` +`python3 -m http.server --directory .` + +# Start LC + +- Safari: http://localhost:8000/?network=hex&bootstrap=%2Fip4%2F209.38.38.158%2Fudp%2F39001%2Fwebrtc-direct%2Fcerthash%2FuEiCVz-CTCrMq4I2xwW_WznQPML3dos4GNWiXE_fJjvHiIg +- Firefox: 0.0.0.0:8000/?network=hex&bootstrap=%2Fip4%2F209.38.38.158%2Fudp%2F39001%2Fwebrtc-direct%2Fcerthash%2FuEiCVz-CTCrMq4I2xwW_WznQPML3dos4GNWiXE_fJjvHiIg diff --git a/web/src/lib.rs b/web/src/lib.rs new file mode 100644 index 000000000..4ea1c91ad --- /dev/null +++ b/web/src/lib.rs @@ -0,0 +1,161 @@ +#![cfg(target_arch = "wasm32")] + +use std::sync::Arc; + +use avail_light_core::data; +use avail_light_core::light_client::OutputEvent as LcEvent; +use avail_light_core::network::{self, p2p, rpc, Network}; +use avail_light_core::shutdown::Controller; +use avail_light_core::types::{Delay, MultiaddrConfig}; +use avail_light_core::utils::spawn_in_span; +use avail_rust::kate_recovery::couscous; +use clap::ValueEnum; +use libp2p::Multiaddr; +use std::str::FromStr; +use tokio::sync::{broadcast, mpsc}; +use tokio_with_wasm::alias as tokio; +use tracing::{error, info, warn}; +use wasm_bindgen::prelude::*; +use web_sys::{window, UrlSearchParams}; +use web_time::Duration; + +#[tokio::main(flavor = "current_thread")] +#[wasm_bindgen(start)] +async fn main_js() {} + +#[wasm_bindgen] +pub async fn run() { + console_error_panic_hook::set_once(); + tracing_wasm::set_as_global_default(); + + let search = window().unwrap().location().search().unwrap(); + let params = UrlSearchParams::new_with_str(&search).unwrap(); + + let mut network = network::Network::Local; + if let Some(value) = params.get("network") { + network = Network::from_str(&value, true).unwrap(); + }; + + let mut bootstrap_multiaddr = network.bootstrap_multiaddr(); + if let Some(value) = params.get("bootstrap") { + bootstrap_multiaddr = Multiaddr::from_str(&value).unwrap(); + }; + + let version = clap::crate_version!(); + let shutdown = Controller::new(); + let db = data::DB::default(); + // TODO: Store and read client_id from local storage + // let client_id = Uuid::new_v4(); + // let execution_id = Uuid::new_v4(); + + let pp = Arc::new(couscous::public_params()); + + let cfg_rpc = rpc::configuration::RPCConfig { + full_node_ws: network.full_node_ws(), + ..Default::default() + }; + + let cfg_libp2p = p2p::configuration::LibP2PConfig { + bootstraps: vec![MultiaddrConfig::PeerIdAndMultiaddr(( + network.bootstrap_peer_id(), + bootstrap_multiaddr, + ))], + ..Default::default() + }; + + let genesis_hash = &network.genesis_hash().to_string(); + let (rpc_event_sender, rpc_event_receiver) = broadcast::channel(1000); + + let (rpc_client, rpc_subscriptions) = rpc::init( + db.clone(), + genesis_hash, + &cfg_rpc, + shutdown.clone(), + rpc_event_sender.clone(), + ) + .await + .unwrap(); + + let (id_keys, _peer_id) = p2p::identity(&cfg_libp2p, db.clone()).unwrap(); + + let project_name = "avail".to_string(); + let (p2p_client, p2p_event_loop, _p2p_event_receiver) = p2p::init( + cfg_libp2p.clone(), + project_name, + id_keys, + version, + "DEV", + false, + shutdown.clone(), + ) + .await + .unwrap(); + + let network_client = network::new(p2p_client.clone(), rpc_client, pp, false); + + // spawn the RPC Network task for Event Loop to run in the background + // and shut it down, without delays + let _rpc_subscriptions_handle = spawn_in_span(shutdown.with_cancel(shutdown.with_trigger( + "Subscription loop failure triggered shutdown".to_string(), + async { + let result = rpc_subscriptions.run().await; + if let Err(ref err) = result { + error!(%err, "Subscription loop ended with error"); + }; + result + }, + ))); + + let (lc_sender, mut lc_receiver) = mpsc::unbounded_channel::(); + let (block_tx, _block_rx) = + broadcast::channel::(1 << 7); + + let channels = avail_light_core::types::ClientChannels { + block_sender: block_tx, + rpc_event_receiver, + }; + + spawn_in_span(async move { + loop { + let Some(message) = lc_receiver.recv().await else { + info!("Exiting..."); + break; + }; + info!("{message:?}"); + } + }); + + let light_client_handle = tokio::task::spawn(avail_light_core::light_client::run( + db.clone(), + network_client, + 99.9, + Delay(Some(Duration::from_secs(20))), + channels, + shutdown.clone(), + lc_sender, + )); + + tokio::task::spawn(p2p_event_loop.run()); + + let bootstraps = cfg_libp2p.bootstraps.clone(); + let bootstrap_p2p_client = p2p_client.clone(); + spawn_in_span(shutdown.with_cancel(async move { + info!("Bootstraping the DHT with bootstrap nodes..."); + let bs_result = bootstrap_p2p_client + .clone() + .bootstrap_on_startup(&bootstraps) + .await; + match bs_result { + Ok(_) => { + info!("Bootstrap done."); + }, + Err(e) => { + warn!("Bootstrap process: {e:?}."); + }, + } + })); + + if let Err(error) = light_client_handle.await { + error!("Error running light client: {error}") + }; +} diff --git a/web/www/index.html b/web/www/index.html new file mode 100644 index 000000000..20a995854 --- /dev/null +++ b/web/www/index.html @@ -0,0 +1,18 @@ + + + + + + Avail Light Client Web + + +

Avail Light Client Web

+ + +