Skip to content

Commit

Permalink
feat(node,node-wasm)!: Integrate graceful shutdown in WASM (#396)
Browse files Browse the repository at this point in the history
Signed-off-by: Yiannis Marangos <psyberbits@gmail.com>
Co-authored-by: zvolin <mac.zwolinski@gmail.com>
  • Loading branch information
oblique and zvolin authored Sep 27, 2024
1 parent f6c703a commit b7b35a6
Show file tree
Hide file tree
Showing 16 changed files with 234 additions and 202 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ mime_guess = "2.0.4"
redb = "2.1.1"
rust-embed = { version = "8.4.0", features = ["interpolate-folder-path"] }
serde = "1.0.203"
serde_repr = "0.1.19"
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
tracing = "0.1.40"
tracing-appender = "0.2.3"
Expand Down
4 changes: 1 addition & 3 deletions cli/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ use std::env::current_exe;
use anyhow::Result;
use clap::{Parser, ValueEnum};
use lumina_node::network::Network;
use serde_repr::Serialize_repr;

use crate::native;
#[cfg(feature = "browser-node")]
use crate::server;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize_repr)]
#[repr(u8)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub(crate) enum ArgNetwork {
#[default]
Mainnet,
Expand Down
79 changes: 36 additions & 43 deletions cli/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,52 @@ use std::net::SocketAddr;

use anyhow::Result;
use axum::body::Body;
use axum::extract::{Path, State};
use axum::extract::Path;
use axum::http::{header, StatusCode};
use axum::response::Response;
use axum::routing::get;
use axum::{Json, Router};
use axum::Router;
use clap::Args;
use libp2p::Multiaddr;
use lumina_node::network::canonical_network_bootnodes;
use rust_embed::RustEmbed;
use serde::Serialize;
use rust_embed::{EmbeddedFile, RustEmbed};
use tokio::net::TcpListener;
use tracing::info;

use crate::common::ArgNetwork;

const SERVER_DEFAULT_BIND_ADDR: &str = "127.0.0.1:9876";

#[derive(Debug, Clone, Serialize)]
struct WasmNodeArgs {
pub network: ArgNetwork,
pub bootnodes: Vec<Multiaddr>,
}

#[derive(RustEmbed)]
#[folder = "$WASM_NODE_OUT_DIR"]
struct WasmPackage;

#[derive(RustEmbed)]
#[folder = "../node-wasm/js"]
struct WrapperPackage;

#[derive(RustEmbed)]
#[folder = "static"]
struct StaticResources;

#[derive(Debug, Args)]
pub(crate) struct Params {
/// Network to connect.
#[arg(short, long, value_enum, default_value_t)]
pub(crate) network: ArgNetwork,

/// Listening addresses. Can be used multiple times.
/// Listening address.
#[arg(short, long = "listen", default_value = SERVER_DEFAULT_BIND_ADDR)]
pub(crate) listen_addr: SocketAddr,

/// Bootnode multiaddr, including peer id. Can be used multiple times.
#[arg(short, long = "bootnode")]
pub(crate) bootnodes: Vec<Multiaddr>,
}

pub(crate) async fn run(args: Params) -> Result<()> {
let network = args.network.into();
let bootnodes = if args.bootnodes.is_empty() {
canonical_network_bootnodes(network).collect()
} else {
args.bootnodes
};

let state = WasmNodeArgs {
network: args.network,
bootnodes,
};

let app = Router::new()
.route("/", get(serve_index_html))
.route("/js/*path", get(serve_embedded_path::<StaticResources>))
.route("/wasm/*path", get(serve_embedded_path::<WasmPackage>))
.route("/cfg.json", get(serve_config))
.with_state(state);
.route("/*path", get(serve_embedded_path::<StaticResources>))
.route(
"/lumina-node/*path",
get(serve_embedded_path::<WrapperPackage>),
)
.route(
"/lumina-node-wasm/*path",
get(serve_embedded_path::<WasmPackage>),
);

info!("listening on {}", args.listen_addr);
let listener = TcpListener::bind(&args.listen_addr).await?;
info!("Address: http://{}", args.listen_addr);

Ok(axum::serve(listener, app.into_make_service()).await?)
}
Expand All @@ -81,8 +59,14 @@ async fn serve_index_html() -> Result<Response, StatusCode> {
async fn serve_embedded_path<Source: RustEmbed>(
Path(path): Path<String>,
) -> Result<Response, StatusCode> {
if let Some(content) = Source::get(&path) {
if let Some(mut content) = Source::get(&path) {
let mime = mime_guess::from_path(&path).first_or_octet_stream();

if mime == mime_guess::mime::APPLICATION_JAVASCRIPT {
// Here be dragons!
patch_imports(&mut content);
}

Ok(Response::builder()
.header(header::CONTENT_TYPE, mime.as_ref())
.body(Body::from(content.data))
Expand All @@ -92,6 +76,15 @@ async fn serve_embedded_path<Source: RustEmbed>(
}
}

async fn serve_config(state: State<WasmNodeArgs>) -> Json<WasmNodeArgs> {
Json(state.0)
/// Patches imports of Javascript files to avoid duplication of them.
fn patch_imports(file: &mut EmbeddedFile) {
file.data = String::from_utf8_lossy(&file.data)
.replace("\"lumina-node\"", "\"/lumina-node/index.js\"")
.replace("\"worker.js\"", "\"/lumina-node/worker.js\"")
.replace(
"\"lumina-node-wasm\"",
"\"/lumina-node-wasm/lumina_node_wasm.js\"",
)
.into_bytes()
.into();
}
16 changes: 14 additions & 2 deletions cli/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<title>Lumina</title>
<script type="module" src="/js/run_node.js"></script>
<script type="module" src="/run_node.js"></script>
<style>
:root {
--bg1: #121212;
Expand Down Expand Up @@ -53,6 +53,18 @@
outline: none;
}

.button {
background-color: var(--bg1);
color: var(--fg1);
border: 1px solid var(--border);
}

.button:focus,
.button:focus-visible {
border: 1px solid var(--fg1);
outline: none;
}

.status {
margin: 1rem 0 1rem 0.5rem;
visibility: hidden;
Expand Down Expand Up @@ -97,7 +109,7 @@ <h3>Bootnodes</h3>
<textarea id="bootnodes" class="config" cols=120 rows=8></textarea>

<div>
<button id="start" class="config"><b>Start!</b></button>
<button id="start-stop" class="button" id="start-stop"><b>Start</b></button>
</div>

<h2 class="event_logs">Event Logs</h2>
Expand Down
154 changes: 68 additions & 86 deletions cli/static/run_node.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,40 @@
Error.stackTraceLimit = 99; // rust stack traces can get pretty big, increase the default

import init, { NodeConfig, NodeClient } from "/wasm/lumina_node_wasm.js";
import { NodeConfig, spawnNode } from "lumina-node";

async function fetch_config() {
const response = await fetch('/cfg.json');
const json = await response.json();

console.log("Received config:", json);

let config = NodeConfig.default(json.network);
if (json.bootnodes.length !== 0) {
config.bootnodes = json.bootnodes;
}

return config;
}

async function show_stats(node) {
if (!node || !await node.is_running()) {
async function showStats(node) {
if (!node || !await node.isRunning()) {
return;
}
const info = await node.syncer_info();
const info = await node.syncerInfo();
document.getElementById("stored-ranges").innerText = info.stored_headers.map((range) => {
return `${range.start}..${range.end}`;
}).join(", ");

let peers_ul = document.createElement('ul');
(await node.connected_peers()).forEach(peer => {
let peersUl = document.createElement('ul');
(await node.connectedPeers()).forEach(peer => {
var li = document.createElement("li");
li.innerText = peer;
li.classList.add("mono");
peers_ul.appendChild(li);
peersUl.appendChild(li);
});

document.getElementById("peers").replaceChildren(peers_ul);
document.getElementById("peers").replaceChildren(peersUl);

const network_head = await node.get_network_head_header();
if (network_head == null) {
return
const networkHead = await node.getNetworkHeadHeader();
if (networkHead == null) {
return;
}

const square_rows = network_head.dah.row_roots.length;
const square_cols = network_head.dah.column_roots.length;
const squareRows = networkHead.dah.row_roots.length;
const squareCols = networkHead.dah.column_roots.length;

document.getElementById("block-height").innerText = network_head.header.height;
document.getElementById("block-hash").innerText = network_head.commit.block_id.hash;
document.getElementById("block-data-square").innerText = `${square_rows}x${square_cols} shares`;
document.getElementById("block-height").innerText = networkHead.header.height;
document.getElementById("block-hash").innerText = networkHead.commit.block_id.hash;
document.getElementById("block-data-square").innerText = `${squareRows}x${squareCols} shares`;
}

function bind_config(data) {
const network_div = document.getElementById("network-id");
const bootnodes_div = document.getElementById("bootnodes");

const update_config_elements = () => {
network_div.value = window.config.network;
bootnodes_div.value = window.config.bootnodes.join("\n");
}

let proxy = {
set: function(obj, prop, value) {
if (prop == "network") {
const config = NodeConfig.default(Number(value));
obj.network = config.network;
obj.bootnodes = config.bootnodes;
} else if (prop == "bootnodes") {
obj[prop] = value;
} else {
return Reflect.set(obj, prop, value);
}

update_config_elements()

return true;
}
};

window.config = new Proxy(data, proxy);
update_config_elements();

network_div.addEventListener("change", event => {
window.config.network = Number(event.target.value.trim());
});
bootnodes_div.addEventListener("change", event => {
window.config.bootnodes = event.target.value.trim().split("\n").map(multiaddr => multiaddr.trim());
});
}

function log_event(event) {
function logEvent(event) {
// Skip noisy events
if (event.data.get("event").type == "share_sampling_result") {
return;
Expand All @@ -105,33 +53,67 @@ function log_event(event) {
textarea.scrollTop = textarea.scrollHeight;
}

async function main(document, window) {
await init();
function starting(document) {
document.getElementById("start-stop").disabled = true;
document.querySelectorAll('.config').forEach(elem => elem.disabled = true);
}

async function started(document, window) {
document.getElementById("peer-id").innerText = await window.node.localPeerId();
document.querySelectorAll(".status").forEach(elem => elem.style.visibility = "visible");
document.getElementById("start-stop").innerText = "Stop";
document.getElementById("start-stop").disabled = false;
window.showStatsIntervalId = setInterval(async () => await showStats(window.node), 1000);
}

function stopping(document, window) {
clearInterval(window.showStatsIntervalId);
document.getElementById("start-stop").disabled = true;
}

window.node = await new NodeClient("/js/worker.js");
function stopped(document) {
document.querySelectorAll(".status").forEach(elem => elem.style.visibility = "hidden");
document.querySelectorAll(".status-value").forEach(elem => elem.innerText = "");
document.getElementById("start-stop").innerText = "Start";
document.querySelectorAll('.config').forEach(elem => elem.disabled = false);
document.getElementById("start-stop").disabled = false;
}

async function main(document, window) {
window.node = await spawnNode();

window.events = await window.node.events_channel();
window.events = await window.node.eventsChannel();
window.events.onmessage = (event) => {
log_event(event);
logEvent(event);
};

bind_config(await fetch_config());
const networkIdDiv = document.getElementById("network-id");
const bootnodesDiv = document.getElementById("bootnodes");
const startStopDiv = document.getElementById("start-stop");

if (await window.node.is_running() === true) {
document.querySelectorAll('.config').forEach(elem => elem.disabled = true);
document.getElementById("peer-id").innerText = await window.node.local_peer_id();
document.querySelectorAll(".status").forEach(elem => elem.style.visibility = "visible");
}
window.config = NodeConfig.default(0);
bootnodesDiv.value = window.config.bootnodes.join("\n");

document.getElementById("start").addEventListener("click", async () => {
document.querySelectorAll('.config').forEach(elem => elem.disabled = true);
networkIdDiv.addEventListener("change", event => {
window.config = NodeConfig.default(Number(event.target.value));
bootnodesDiv.value = window.config.bootnodes.join("\n");
});

await window.node.start(window.config);
document.getElementById("peer-id").innerText = await window.node.local_peer_id();
document.querySelectorAll(".status").forEach(elem => elem.style.visibility = "visible");
bootnodesDiv.addEventListener("change", event => {
window.config.bootnodes = event.target.value.trim().split("\n").map(multiaddr => multiaddr.trim());
});

setInterval(async () => await show_stats(window.node), 1000)
startStopDiv.addEventListener("click", async () => {
if (await window.node.isRunning() === true) {
stopping(document, window);
await window.node.stop();
stopped(document);
} else {
starting(document);
await window.node.start(window.config);
await started(document, window);
}
});
}

await main(document, window);
Loading

0 comments on commit b7b35a6

Please sign in to comment.