diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f74e593..dad03ab4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,9 +80,6 @@ jobs: - name: Test node crate run: wasm-pack test --headless --chrome node - - name: Test node-wasm crate - run: wasm-pack test --headless --chrome node-wasm - - name: Build and pack node-wasm run: wasm-pack build --release --target web node-wasm && wasm-pack pack node-wasm @@ -170,6 +167,11 @@ jobs: - name: Run rpc wasm test run: wasm-pack test --headless --chrome rpc --features=wasm-bindgen + - name: Test node-wasm crate + # We're running node-wasm tests in release mode to get around a failing debug assertion + # https://github.com/libp2p/rust-libp2p/issues/5618 + run: wasm-pack test --headless --release --chrome node-wasm + unused-deps: diff --git a/Cargo.lock b/Cargo.lock index c246ec72..66d927ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,6 +3320,7 @@ version = "0.6.1" dependencies = [ "anyhow", "blockstore", + "celestia-rpc", "celestia-tendermint", "celestia-types", "console_error_panic_hook", @@ -3330,6 +3331,7 @@ dependencies = [ "js-sys", "libp2p", "lumina-node", + "rexie", "serde", "serde-wasm-bindgen", "serde_json", @@ -4159,7 +4161,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.5.0", + "heck 0.4.1", "itertools 0.12.1", "log", "multimap", diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 69f93e6f..81191648 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -6,7 +6,7 @@ services: context: . dockerfile: Dockerfile.validator environment: - # provide amount of bridge nodes to provision (default: 1) + # provide amount of bridge nodes to provision (default: 2) - BRIDGE_COUNT=2 volumes: - credentials:/credentials @@ -22,6 +22,8 @@ services: # provide an id for the bridge node (default: 0) # each node should have a next natural number starting from 0 - NODE_ID=0 + # setting SKIP_AUTH to true disables the use of JWT for authentication + - SKIP_AUTH=true ports: - 26658:26658 volumes: @@ -38,8 +40,6 @@ services: # provide an id for the bridge node (default: 0) # each node should have a next natural number starting from 0 - NODE_ID=1 - # setting SKIP_AUTH to true disables the use of JWT for authentication - - SKIP_AUTH=true ports: - 36658:26658 volumes: @@ -57,9 +57,9 @@ services: # environment: # # provide an id for the bridge node (default: 0) # # each node should have a next natural number starting from 0 - # - NODE_ID=1 + # - NODE_ID=2 # ports: - # - 36658:26658 + # - 46658:26658 # volumes: # - credentials:/credentials # - genesis:/genesis diff --git a/ci/run-bridge.sh b/ci/run-bridge.sh index d14b3fb5..07702aad 100755 --- a/ci/run-bridge.sh +++ b/ci/run-bridge.sh @@ -52,7 +52,7 @@ whitelist_localhost_nodes() { # cargo run -- node -n private -l 0.0.0.0 # docker compose -f ci/docker-compose.yml exec bridge-0 celestia p2p peer-info $lumina_peerid dasel put -f "$CONFIG_DIR/config.toml" \ - -t json -v '["172.18.0.1/24", "172.17.0.1/24", "192.168.0.0/16"]' \ + -t json -v '["172.16.0.0/12", "192.168.0.0/16"]' \ 'P2P.IPColocationWhitelist' } @@ -61,6 +61,16 @@ write_jwt_token() { celestia bridge auth admin --p2p.network "$P2P_NETWORK" > "$NODE_JWT_FILE" } +connect_to_common_bridge() { + # wait for nodes to spin up + sleep 5 + # get PeerId of the common node + local peer_id=$(celestia p2p info --url 'ws://bridge-0:26658' | jq -r '.result.id') + # connect to it + echo "Connecting to $peer_id: /dns/bridge-0/tcp/2121" + celestia p2p connect "$peer_id" "/dns/bridge-0/tcp/2121" +} + main() { # Initialize the bridge node celestia bridge init --p2p.network "$P2P_NETWORK" @@ -76,6 +86,10 @@ main() { write_jwt_token # give validator some time to set up sleep 4 + # each node without SKIP_AUTH connects to the one with, so that bridges can discover eachother + if [ ! "$SKIP_AUTH" == "true" ] ; then + connect_to_common_bridge & + fi # Start the bridge node echo "Configuration finished. Running a bridge node..." celestia bridge start \ diff --git a/cli/src/native.rs b/cli/src/native.rs index 09967833..aa636f64 100644 --- a/cli/src/native.rs +++ b/cli/src/native.rs @@ -20,7 +20,7 @@ use tracing::warn; use crate::common::ArgNetwork; -const CELESTIA_LOCAL_BRIDGE_RPC_ADDR: &str = "ws://localhost:26658"; +const CELESTIA_LOCAL_BRIDGE_RPC_ADDR: &str = "ws://localhost:36658"; #[derive(Debug, Parser)] pub(crate) struct Params { diff --git a/node-wasm/Cargo.toml b/node-wasm/Cargo.toml index 6ac2ef63..2fc42e4d 100644 --- a/node-wasm/Cargo.toml +++ b/node-wasm/Cargo.toml @@ -75,6 +75,8 @@ web-sys = { version = "0.3.70", features = [ [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.42" +celestia-rpc = { workspace = true, features = ["wasm-bindgen", "p2p"] } +rexie = "0.6.2" [package.metadata.docs.rs] targets = ["wasm32-unknown-unknown"] diff --git a/node-wasm/src/client.rs b/node-wasm/src/client.rs index a22ca55f..d9eaafd5 100644 --- a/node-wasm/src/client.rs +++ b/node-wasm/src/client.rs @@ -422,3 +422,131 @@ impl WasmNodeConfig { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + use std::time::Duration; + + use celestia_rpc::{prelude::*, Client}; + use celestia_types::p2p::PeerId; + use celestia_types::ExtendedHeader; + use gloo_timers::future::sleep; + use libp2p::{multiaddr::Protocol, Multiaddr}; + use rexie::Rexie; + use serde_wasm_bindgen::from_value; + use wasm_bindgen_futures::spawn_local; + use wasm_bindgen_test::wasm_bindgen_test; + use web_sys::MessageChannel; + + use crate::worker::NodeWorker; + + // uses bridge-0, which has skip-auth enabled + const WS_URL: &str = "ws://127.0.0.1:26658"; + + #[wasm_bindgen_test] + async fn request_network_head_header() { + remove_database().await.expect("failed to clear db"); + let rpc_client = Client::new(WS_URL).await.unwrap(); + let bridge_ma = fetch_bridge_webtransport_multiaddr(&rpc_client).await; + + let client = spawn_connected_node(vec![bridge_ma.to_string()]).await; + + let info = client.network_info().await.unwrap(); + assert_eq!(info.num_peers, 1); + + let bridge_head_header = rpc_client.header_network_head().await.unwrap(); + let head_header: ExtendedHeader = + from_value(client.request_head_header().await.unwrap()).unwrap(); + assert_eq!(head_header, bridge_head_header); + rpc_client + .p2p_close_peer(&PeerId( + client.local_peer_id().await.unwrap().parse().unwrap(), + )) + .await + .unwrap(); + } + + #[wasm_bindgen_test] + async fn discover_network_peers() { + crate::utils::setup_logging(); + remove_database().await.expect("failed to clear db"); + let rpc_client = Client::new(WS_URL).await.unwrap(); + let bridge_ma = fetch_bridge_webtransport_multiaddr(&rpc_client).await; + + let client = spawn_connected_node(vec![bridge_ma.to_string()]).await; + + let info = client.network_info().await.unwrap(); + assert_eq!(info.num_peers, 1); + + sleep(Duration::from_millis(300)).await; + + client.wait_connected().await.unwrap(); + let info = client.network_info().await.unwrap(); + assert_eq!(info.num_peers, 2); + rpc_client + .p2p_close_peer(&PeerId( + client.local_peer_id().await.unwrap().parse().unwrap(), + )) + .await + .unwrap(); + } + + async fn spawn_connected_node(bootnodes: Vec) -> NodeClient { + let message_channel = MessageChannel::new().unwrap(); + let mut worker = NodeWorker::new(message_channel.port1().into()); + + spawn_local(async move { + worker.run().await.unwrap(); + }); + + let client = NodeClient::new(message_channel.port2().into()) + .await + .unwrap(); + assert!(!client.is_running().await.expect("node ready to be run")); + + client + .start(&WasmNodeConfig { + network: Network::Private, + bootnodes, + custom_syncing_window_secs: None, + }) + .await + .unwrap(); + assert!(client.is_running().await.expect("running node")); + client.wait_connected_trusted().await.expect("to connect"); + + client + } + + async fn fetch_bridge_webtransport_multiaddr(client: &Client) -> Multiaddr { + let bridge_info = client.p2p_info().await.unwrap(); + + let mut ma = bridge_info + .addrs + .into_iter() + .find(|ma| { + let not_localhost = !ma + .iter() + .any(|prot| prot == Protocol::Ip4("127.0.0.1".parse().unwrap())); + let webtransport = ma + .protocol_stack() + .any(|protocol| protocol == "webtransport"); + not_localhost && webtransport + }) + .expect("Bridge doesn't listen on webtransport"); + + if !ma.protocol_stack().any(|protocol| protocol == "p2p") { + ma.push(Protocol::P2p(bridge_info.id.into())) + } + + ma + } + + async fn remove_database() -> rexie::Result<()> { + Rexie::delete("private").await?; + Rexie::delete("private-blockstore").await?; + Ok(()) + } +} diff --git a/node-wasm/src/worker.rs b/node-wasm/src/worker.rs index 2149b77f..b855624d 100644 --- a/node-wasm/src/worker.rs +++ b/node-wasm/src/worker.rs @@ -40,8 +40,12 @@ pub enum WorkerError { NodeError(Error), } +/// `NodeWorker` is responsible for receiving commands from connected [`NodeClient`]s, executing +/// them and sending a response back, as well as accepting new `NodeClient` connections. +/// +/// [`NodeClient`]: crate::client::NodeClient #[wasm_bindgen] -struct NodeWorker { +pub struct NodeWorker { event_channel_name: String, node: Option, request_server: WorkerServer, diff --git a/node/tests/node.rs b/node/tests/node.rs index 0be30aae..5cda14f2 100644 --- a/node/tests/node.rs +++ b/node/tests/node.rs @@ -29,7 +29,7 @@ async fn connects_to_the_go_bridge_node() { let (node, _) = new_connected_node().await; let info = node.network_info().await.unwrap(); - assert_eq!(info.num_peers(), 1); + assert!(info.num_peers() >= 1); } #[tokio::test] diff --git a/node/tests/utils/mod.rs b/node/tests/utils/mod.rs index 8929e42f..08682382 100644 --- a/node/tests/utils/mod.rs +++ b/node/tests/utils/mod.rs @@ -15,7 +15,7 @@ use lumina_node::{node::Node, store::InMemoryStore}; use tokio::sync::Mutex; use tokio::time::sleep; -const WS_URL: &str = "ws://localhost:26658"; +const WS_URL: &str = "ws://localhost:36658"; pub async fn bridge_client() -> Client { let _ = dotenvy::dotenv(); diff --git a/rpc/README.md b/rpc/README.md index 579dff43..0e5b22bb 100644 --- a/rpc/README.md +++ b/rpc/README.md @@ -11,7 +11,7 @@ use celestia_types::{AppVersion, Blob, TxConfig, nmt::Namespace}; async fn submit_blob() { // create a client to the celestia node let token = std::env::var("CELESTIA_NODE_AUTH_TOKEN").expect("Token not provided"); - let client = Client::new("ws://localhost:26658", Some(&token)) + let client = Client::new("ws://localhost:36658", Some(&token)) .await .expect("Failed creating rpc client"); diff --git a/rpc/tests/utils/client.rs b/rpc/tests/utils/client.rs index 55beb7de..7bd91250 100644 --- a/rpc/tests/utils/client.rs +++ b/rpc/tests/utils/client.rs @@ -10,7 +10,7 @@ use jsonrpsee::core::client::SubscriptionClientT; use jsonrpsee::core::ClientError; use tokio::sync::{Mutex, MutexGuard}; -const CELESTIA_RPC_URL: &str = "ws://localhost:26658"; +const CELESTIA_RPC_URL: &str = "ws://localhost:36658"; async fn write_lock() -> MutexGuard<'static, ()> { static LOCK: OnceLock> = OnceLock::new(); diff --git a/rpc/tests/wasm.rs b/rpc/tests/wasm.rs index bb29c1a1..7a02026e 100644 --- a/rpc/tests/wasm.rs +++ b/rpc/tests/wasm.rs @@ -4,8 +4,8 @@ use celestia_rpc::client::Client; use celestia_rpc::prelude::*; use wasm_bindgen_test::*; -// uses bridge-1, which has skip-auth enabled -const CELESTIA_RPC_URL: &str = "ws://localhost:36658"; +// uses bridge-0, which has skip-auth enabled +const CELESTIA_RPC_URL: &str = "ws://localhost:26658"; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); diff --git a/tools/gen_auth_tokens.sh b/tools/gen_auth_tokens.sh index a31e81a8..e75a1f59 100755 --- a/tools/gen_auth_tokens.sh +++ b/tools/gen_auth_tokens.sh @@ -19,7 +19,7 @@ wait_for_docker_setup() { # wait for the service to start while :; do - curl http://127.0.0.1:26658 > /dev/null 2>&1 && break + curl http://127.0.0.1:36658 > /dev/null 2>&1 && break sleep 1 done } @@ -41,7 +41,7 @@ ensure_dotenv_file() { generate_token() { local auth_level="$1" - docker compose -f "$DOCKER_COMPOSE_FILE" exec -T bridge-0 \ + docker compose -f "$DOCKER_COMPOSE_FILE" exec -T bridge-1 \ celestia bridge auth "$auth_level" --p2p.network private }