diff --git a/.cargo/config.toml b/.cargo/config.toml index 145e7b29c..9ec3807c2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,3 +3,25 @@ rustflags = [] [target.aarch64-apple-darwin] rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"] + +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", + "target-feature=+atomics,+bulk-memory", + "-C", + "link-args=--shared-memory", + "-C", + "link-args=--import-memory", + "-C", + "link-args=--export-memory", + "-C", + "link-args=--max-memory=4294967296", + "-C", + "link-args=--export=__wasm_init_tls", + "-C", + "link-args=--export=__tls_size", + "-C", + "link-args=--export=__tls_align", + "-C", + "link-args=--export=__tls_base", +] diff --git a/.github/scripts/test-misc-mina-key-pair.sh b/.github/scripts/test-misc-mina-key-pair.sh new file mode 100755 index 000000000..5f953fe60 --- /dev/null +++ b/.github/scripts/test-misc-mina-key-pair.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +set -euo pipefail + +# Test the mina misc mina-key-pair command + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$REPO_ROOT" + +MINA_BIN="${MINA_BIN:-./target/release/mina}" + +echo "Testing: mina misc mina-key-pair" +echo "" + +# Test 1: Basic mina-key-pair command +echo "Test 1: mina misc mina-key-pair (basic)" +if "$MINA_BIN" misc mina-key-pair > /dev/null 2>&1; then + echo "✓ Command executed successfully" +else + echo "✗ Test failed: Command failed to execute" + exit 1 +fi +echo "" + +# Test 2: mina-key-pair with --web-node-secrets flag +echo "Test 2: mina misc mina-key-pair --web-node-secrets" +OUTPUT=$("$MINA_BIN" misc mina-key-pair --web-node-secrets) +EXIT_STATUS=$? + +# Verify command executed successfully +if [ $EXIT_STATUS -ne 0 ]; then + echo "✗ Test failed: Command failed to execute" + exit 1 +fi +echo "✓ Command executed successfully" + +# Verify output is valid JSON +if ! echo "$OUTPUT" | jq empty 2>/dev/null; then + echo "✗ Test failed: Output is not valid JSON" + echo "Output was: $OUTPUT" + exit 1 +fi +echo "✓ Output is valid JSON" + +# Verify JSON contains "publicKey" field with a string value +PUBLIC_KEY=$(echo "$OUTPUT" | jq -r '.publicKey' 2>/dev/null) +if [ -z "$PUBLIC_KEY" ] || [ "$PUBLIC_KEY" = "null" ]; then + echo "✗ Test failed: JSON does not contain 'publicKey' field with a string value" + echo "Output was: $OUTPUT" + exit 1 +fi +echo "✓ JSON contains 'publicKey' field with value: $PUBLIC_KEY" + +# Verify JSON contains "privateKey" field with a string value +PRIVATE_KEY=$(echo "$OUTPUT" | jq -r '.privateKey' 2>/dev/null) +if [ -z "$PRIVATE_KEY" ] || [ "$PRIVATE_KEY" = "null" ]; then + echo "✗ Test failed: JSON does not contain 'privateKey' field with a string value" + echo "Output was: $OUTPUT" + exit 1 +fi +echo "✓ JSON contains 'privateKey' field with value: [REDACTED]" + +echo "" +echo "✓ All tests passed!" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 61d0a174b..ec91e9a43 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -228,6 +228,9 @@ jobs: - name: Test OCaml-specific GraphQL endpoints run: ./.github/scripts/test-ocaml-specific-endpoints.sh + - name: Test mina misc mina-key-pair command + run: ./.github/scripts/test-misc-mina-key-pair.sh + - name: Upload binaries uses: actions/upload-artifact@v5 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b13cbf35..85844a855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#1736](https://github.com/o1-labs/mina-rust/pull/1736)) - **CI**: automatically push Docker images for `vX.Y.Z` and `vX.Y` when tag `vX.Y.Z` is created ([#1838](https://github.com/o1-labs/mina-rust/pull/1838)) +- **Web Node**: fix webnode build, add quality-of-life improvements when running + webnode in Docker. ([#1778](https://github.com/o1-labs/mina-rust/pull/1778)) ### Changes diff --git a/Cargo.lock b/Cargo.lock index 05d3ff856..b35266e13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,9 +629,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2 1.0.95", "quote 1.0.35", @@ -2892,9 +2892,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -2916,9 +2916,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -2926,15 +2926,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -2944,9 +2944,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -2978,9 +2978,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2 1.0.95", "quote 1.0.35", @@ -2999,15 +2999,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-ticker" @@ -3028,9 +3028,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -4092,9 +4092,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -4291,9 +4291,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libp2p" @@ -5364,6 +5364,16 @@ dependencies = [ "zstd", ] +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -5754,6 +5764,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "num" version = "0.4.1" @@ -9390,34 +9409,22 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" -dependencies = [ - "bumpalo", - "log", - "proc-macro2 1.0.95", - "quote 1.0.35", - "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -9428,9 +9435,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote 1.0.35", "wasm-bindgen-macro-support", @@ -9438,32 +9445,42 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2 1.0.95", "quote 1.0.35", "syn 2.0.96", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" dependencies = [ - "console_error_panic_hook", + "async-trait", + "cast", "js-sys", - "scoped-tls", + "libm", + "minicov", + "nu-ansi-term 0.50.3", + "num-traits", + "oorandom", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -9471,12 +9488,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2 1.0.95", "quote 1.0.35", + "syn 2.0.96", ] [[package]] @@ -9508,9 +9526,9 @@ dependencies = [ [[package]] name = "wasm_thread" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c4b28d4dd9c0736a6d8ab300a6ea7a643dcb95f43ec08ad7c45d3bbee476f7" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" dependencies = [ "futures", "js-sys", @@ -9520,9 +9538,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 571216bf0..2827a5e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -237,10 +237,10 @@ vergen = { version = "8.2.4", features = [ ] } vrf = { path = "vrf" } warp = "0.3" -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -wasm-bindgen-test = "0.3.33" -wasm_thread = { version = "0.3", features = ["es_modules"] } +wasm-bindgen = "^0.2.106" +wasm-bindgen-futures = "^0.4.56" +wasm-bindgen-test = "^0.3.56" +wasm_thread = { version = "^0.3.3", features = ["es_modules"] } web-sys = { version = "0.3.64" } webrtc = { git = "https://github.com/openmina/webrtc.git", rev = "aeaa62682b97f6984627bedd6e6811fe17af18eb" } x25519-dalek = { version = "2.0.1", features = ["static_secrets"] } diff --git a/Makefile b/Makefile index 0f4bae7fa..0a2c432b2 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ NIGHTLY_RUST_VERSION = "nightly" NODE_VERSION := $(shell cat .nvmrc) # WebAssembly -WASM_BINDGEN_CLI_VERSION = "0.2.99" +WASM_BINDGEN_CLI_VERSION = "0.2.106" # TOML formatter TAPLO_CLI_VERSION = "0.9.3" @@ -37,6 +37,11 @@ NETWORK ?= devnet VERBOSITY ?= info GIT_COMMIT := $(shell git rev-parse --short=8 HEAD) +# Circuit Blobs +CIRCUITS_REPO ?= https://github.com/o1-labs/circuit-blobs.git +CIRCUITS_REV ?= main +CIRCUITS_NETWORKS ?= 3.0.0mainnet berkeley-devnet + # Documentation server port DOCS_PORT ?= 3000 @@ -214,9 +219,12 @@ clean: ## Clean build artifacts .PHONY: download-circuits download-circuits: ## Download the circuits used by Mina from GitHub @if [ ! -d "circuit-blobs" ]; then \ - git clone --depth 1 https://github.com/o1-labs/circuit-blobs.git -b dw/add-berkeley-687bf44e97328e1cc0e85291663009410f64bd99; \ - ln -s "$$PWD"/circuit-blobs/3.0.0mainnet ledger/; \ - ln -s "$$PWD"/circuit-blobs/berkeley-devnet ledger/; \ + git clone --depth 1 $(CIRCUITS_REPO) -b $(CIRCUITS_REV); \ + for network in $(CIRCUITS_NETWORKS); do \ + echo "Including circuits for $$network"; \ + rm -f "$$PWD"/ledger/"$$network"; \ + ln -s "$$PWD"/circuit-blobs/"$$network" ledger/"$$network"; \ + done; \ else \ echo "circuit-blobs already exists, skipping download."; \ fi diff --git a/cli/src/commands/misc.rs b/cli/src/commands/misc.rs index 4308f4db6..4ddb0d0aa 100644 --- a/cli/src/commands/misc.rs +++ b/cli/src/commands/misc.rs @@ -1,4 +1,5 @@ use libp2p_identity::PeerId; +use mina_node_account::AccountPublicKey; use node::{account::AccountSecretKey, p2p::identity::SecretKey}; use std::{fs::File, io::Write}; @@ -46,18 +47,56 @@ impl P2PKeyPair { } } -#[derive(Debug, Clone, clap::Args)] +/// Generates an unencrypted Mina key pair. +/// +/// Note: When `--web-node-secrets` is set we output a +/// JSON file with the structure below. The webnode +/// needs a key in this format to initialize, even if +/// no block production is intended. This flag will be +/// removed when the webnode no longer needs it, and exists +/// mainly to simplify the setup process of a webnode. +/// ```json +/// { +/// "publicKey": "{the public key derived from secret_key}", +/// "privateKey": "{secret_key}", +/// } +/// ``` +#[derive(Debug, Clone, Default, clap::Args)] pub struct MinaKeyPair { #[arg(long, short = 's', env = "MINA_SEC_KEY")] secret_key: Option, + + #[arg(long, help = "Format as a web-node-secrets.json")] + web_node_secrets: bool, +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct GeneratedMinaKeyPair { + public_key: AccountPublicKey, + private_key: AccountSecretKey, +} + +impl GeneratedMinaKeyPair { + fn print(&self) { + println!("secret key: {}", self.private_key); + println!("public key: {}", self.public_key); + } } impl MinaKeyPair { pub fn run(self) -> anyhow::Result<()> { - let secret_key = self.secret_key.unwrap_or_else(AccountSecretKey::rand); - let public_key = secret_key.public_key(); - println!("secret key: {secret_key}"); - println!("public key: {public_key}"); + let private_key = self.secret_key.unwrap_or_else(AccountSecretKey::rand); + let public_key = private_key.public_key(); + let keypair = GeneratedMinaKeyPair { + public_key, + private_key, + }; + if self.web_node_secrets { + println!("{}", serde_json::to_string_pretty(&keypair)?); + } else { + keypair.print(); + } Ok(()) } @@ -304,7 +343,7 @@ mod tests { #[test] fn test_mina_key_pair_generates_random_key() { - let cmd = MinaKeyPair { secret_key: None }; + let cmd = MinaKeyPair::default(); let result = cmd.run(); assert!(result.is_ok()); @@ -315,6 +354,7 @@ mod tests { let secret_key = AccountSecretKey::rand(); let cmd = MinaKeyPair { secret_key: Some(secret_key), + web_node_secrets: false, }; let result = cmd.run(); diff --git a/frontend/Dockerfile b/frontend/Dockerfile index e02cb06da..aa8ad1069 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -27,6 +27,10 @@ # REQUIRED ENVIRONMENT VARIABLES: # MINA_FRONTEND_ENVIRONMENT - Frontend configuration (local|webnode|production) # +# OPTIONAL ENVIRONMENT VARIABLES: +# MINA_WEBNODE_SEED_URLS - Comma separated list of HTTP(S) urls to fetch bootnodes from +# MINA_WEBNODE_BOOTNODES - Comma separated list of multiaddrs to use as bootnodes +# # USAGE: # # Generate .env.docker with current git information # ./generate-docker-env.sh @@ -62,6 +66,7 @@ RUN make setup-wasm # Copy source code required for WASM build COPY Cargo.toml Cargo.lock ./ +COPY .cargo/config.toml ./.cargo/config.toml COPY cli ./cli COPY core ./core COPY fuzzer ./fuzzer @@ -137,11 +142,11 @@ COPY Makefile ./ COPY frontend/httpd.conf /usr/local/apache2/conf/httpd.conf # Copy WASM files from build stage -COPY --from=wasm_files . /usr/local/apache2/htdocs/assets/webnode/pkg/ +COPY --from=wasm_files . /usr/local/apache2/htdocs/assets/webnode/ # Copy circuit blobs for devnet and mainnet from local directory -COPY circuit-blobs/3.0.1devnet /usr/local/apache2/htdocs/assets/webnode/circuit-blobs/3.0.1devnet COPY circuit-blobs/3.0.0mainnet /usr/local/apache2/htdocs/assets/webnode/circuit-blobs/3.0.0mainnet +COPY circuit-blobs/berkeley-devnet /usr/local/apache2/htdocs/assets/webnode/circuit-blobs/berkeley-devnet # Create startup script and setup directories COPY ./frontend/docker/startup.sh /usr/local/bin/startup.sh diff --git a/frontend/Makefile b/frontend/Makefile index a2d13dbe4..c9fb97a43 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -1,5 +1,9 @@ # Frontend Makefile +# These are threaded from the outside env into build-webnode +MINA_WEBNODE_SEED_URLS ?= +MINA_WEBNODE_BOOTNODES ?= + .PHONY: help help: ## Display this help message @echo "Frontend Makefile - Available targets:" @@ -38,7 +42,10 @@ build-production: ## Build the frontend with production configuration .PHONY: build-webnode build-webnode: ## Build the frontend with webnode configuration - npx ng build --configuration webnode-local + npx ng build \ + --define MINA_WEBNODE_SEED_URLS="\"$(MINA_WEBNODE_SEED_URLS)\"" \ + --define MINA_WEBNODE_BOOTNODES="\"$(MINA_WEBNODE_BOOTNODES)\"" \ + --configuration webnode-local cp dist/frontend/browser/assets/environments/webnode.js \ dist/frontend/browser/assets/environments/env.js diff --git a/frontend/angular.json b/frontend/angular.json index 63c65433a..a30d38e2b 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -62,7 +62,11 @@ }, "scripts": [ "node_modules/aos/dist/aos.js" - ] + ], + "define": { + "MINA_WEBNODE_BOOTNODES": "\"\"", + "MINA_WEBNODE_SEED_URLS": "\"\"" + } }, "configurations": { "production": { diff --git a/frontend/scripts/download-webnode.sh b/frontend/scripts/download-webnode.sh index a02d3335e..3b5affb78 100644 --- a/frontend/scripts/download-webnode.sh +++ b/frontend/scripts/download-webnode.sh @@ -1,12 +1,12 @@ #!/bin/bash # Set the base URL for OpenMina -MINA_BASE_URL="https://github.com/openmina" +MINA_BASE_URL="https://github.com/o1-labs" # Function to download circuit files download_circuit_files() { CIRCUITS_BASE_URL="$MINA_BASE_URL/circuit-blobs/releases/download" - CIRCUITS_VERSION="3.0.1devnet" + CIRCUITS_VERSION="berkeley-devnet" DEVNET_CIRCUIT_FILES=( "block_verifier_index.postcard" diff --git a/frontend/src/app/core/services/web-node.service.ts b/frontend/src/app/core/services/web-node.service.ts index 70f444de5..ec37aa2cb 100644 --- a/frontend/src/app/core/services/web-node.service.ts +++ b/frontend/src/app/core/services/web-node.service.ts @@ -144,6 +144,13 @@ export class WebNodeService { return of(any(window).webnode).pipe( switchMap((wasm: any) => { this.wasm$.next(wasm); + + // nb: this is RUSTFLAGS "-Clink-args=--max-memory=4294967296" (4GiB) + // nb: in .cargo/config.toml for the wasm32 target the div by 65536 + // nb: is because the WASM web API requires memory size to be specified + // nb: in terms of 64KiB *pages* + // todo: move to wherever angular injects `this.memory` + this.memory.maximum = 4294967296 / 65536; return from( wasm.default(undefined, new WebAssembly.Memory(this.memory)), ).pipe(map(() => wasm)); @@ -154,13 +161,13 @@ export class WebNodeService { if (typeof this.webNodeNetwork === 'number') { const url = `${window.location.origin}/clusters/${this.webNodeNetwork}/`; return { - seeds: url + 'seeds', + seedUrls: [url + 'seeds'], genesisConfig: url + 'genesis/config', }; } else { return { - seeds: - 'https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt', + seedUrls: CONFIG.globalConfig.webNodeSeedUrls, + fixedSeeds: CONFIG.globalConfig.webNodeBootNodes, }; } })(); @@ -177,7 +184,14 @@ export class WebNodeService { privateKey = null; } - return from(wasm.run(privateKey, urls.seeds, urls.genesisConfig)); + return from( + wasm.run( + privateKey, + urls.seedUrls, + urls.fixedSeeds, + urls.genesisConfig, + ), + ); }), tap((webnode: any) => { any(window).webnode = webnode; diff --git a/frontend/src/app/shared/constants/config.ts b/frontend/src/app/shared/constants/config.ts index cd8df1ba4..a2813f40b 100644 --- a/frontend/src/app/shared/constants/config.ts +++ b/frontend/src/app/shared/constants/config.ts @@ -56,6 +56,7 @@ export function getFirstFeature( console.log('getFirstFeature called with config:', { config, 'CONFIG.configs': CONFIG.configs, + globalConfig: CONFIG.globalConfig, }); if (Array.isArray(config?.features)) { diff --git a/frontend/src/app/shared/types/core/environment/mina-env.type.ts b/frontend/src/app/shared/types/core/environment/mina-env.type.ts index b1a00676a..602920e84 100644 --- a/frontend/src/app/shared/types/core/environment/mina-env.type.ts +++ b/frontend/src/app/shared/types/core/environment/mina-env.type.ts @@ -55,6 +55,12 @@ export interface MinaEnv { features?: FeaturesConfig; /** GraphQL endpoint URL for blockchain queries */ graphQL?: string; + + /** For WebNodes, optionally supply HTTP(S) URLs to fetch bootnodes from */ + webNodeSeedUrls?: Readonly; + + /** For WebNodes, optionally supply multiaddrs of known bootnodes */ + webNodeBootNodes?: Readonly; }; } diff --git a/frontend/src/environments/environment.webnode-local.ts b/frontend/src/environments/environment.webnode-local.ts index bcb14c97b..0a2e570d7 100644 --- a/frontend/src/environments/environment.webnode-local.ts +++ b/frontend/src/environments/environment.webnode-local.ts @@ -1,5 +1,36 @@ import { MinaEnv } from '@shared/types/core/environment/mina-env.type'; +/** Substituted on launch of docker container from MINA_WEBNODE_SEED_URLS by `ng build` */ +declare const MINA_WEBNODE_SEED_URLS: string; + +/** Default list of seed URLs if MINA_WEBNODE_SEED_URLS isn't specified */ +const defaultWebNodeSeedUrls: Readonly = [ + 'https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt', + '/webnode/pkg/devnet-webrtc.txt', +]; + +/** Substituted on launch of docker container from MINA_WEBNODE_BOOTNODES by `ng build` */ +declare const MINA_WEBNODE_BOOTNODES: string; + +/** Default list of bootnodes if MINA_WEBNODE_BOOTNODES is unspecified */ +const defaultWebNodeBootNodes: Readonly = [ + // example: + // "/2az589QvS6i3EJiVKUfVHCkyqf4khGy9PjQF7nSQuveF27wp7xX/https/mina-rust-seed-1.gcp.o1test.net/443", +]; + +function commaSeparatedEnv( + envVal: string, + fallback: Readonly, +): Readonly { + const envTrimmed = envVal.trim(); + if (envTrimmed.length !== 0) { + const envValue = envTrimmed.split(',').map(s => s.trim()); + return envValue; + } + + return fallback; +} + export const environment: Readonly = { production: true, identifier: 'Web Node FE', @@ -15,6 +46,14 @@ export const environment: Readonly = { mempool: [], benchmarks: ['wallets'], }, + webNodeSeedUrls: commaSeparatedEnv( + MINA_WEBNODE_SEED_URLS ?? '', + defaultWebNodeSeedUrls, + ), + webNodeBootNodes: commaSeparatedEnv( + MINA_WEBNODE_BOOTNODES ?? '', + defaultWebNodeBootNodes, + ), }, configs: [ { diff --git a/frontend/src/index.html b/frontend/src/index.html index 415960fdc..396033778 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -76,8 +76,8 @@ if (typeof window !== 'undefined') { window.addEventListener('startWebNode', async () => { const possiblePaths = [ - '/webnode/pkg/mina_node_web.js', // Production/CI path - '../public/webnode/pkg/mina_node_web.js', // Development path + '/assets/webnode/pkg/mina_node_web.js', // Production/CI path + '/webnode/pkg/mina_node_web.js', // Development path ]; for (const path of possiblePaths) { diff --git a/node/web/src/lib.rs b/node/web/src/lib.rs index 747ed05e6..ddb29ccba 100644 --- a/node/web/src/lib.rs +++ b/node/web/src/lib.rs @@ -1,6 +1,5 @@ #![cfg(target_family = "wasm")] -use ::node::transition_frontier::genesis::GenesisConfig; pub use mina_node_common::*; mod rayon; @@ -11,8 +10,9 @@ pub use node::{Node, NodeBuilder}; use ::node::{ account::AccountSecretKey, - core::thread, + core::{log, thread}, snark::{BlockVerifier, TransactionVerifier}, + transition_frontier::genesis::GenesisConfig, }; use anyhow::Context; use gloo_utils::format::JsValueSerdeExt; @@ -28,7 +28,7 @@ fn main() { thread::main_thread_init(); wasm_bindgen_futures::spawn_local(async { console_error_panic_hook::set_once(); - tracing::initialize(tracing::Level::INFO); + tracing::initialize(tracing::Level::DEBUG); init_rayon().await.unwrap(); }); @@ -74,10 +74,57 @@ fn parse_bp_key(key: JsValue) -> Option { ) } +/// Starts a Mina node in a WASM environment and returns an RPC interface. +/// +/// This is the main entry point for running a Mina node from JavaScript/WASM. +/// It spawns the node in a separate thread, sets up all necessary components, +/// and returns an RPC sender that can be used to communicate with the running +/// node. +/// +/// # Arguments +/// +/// * `block_producer` - Block producer configuration as a JavaScript value. +/// Can be one of: +/// - `null`/`undefined`: No block production +/// - `string`: Plain text secret key +/// - `[encrypted_key, password]`: Array with encrypted key and password +/// * `seed_nodes_urls` - Optional list of URLs to fetch peer lists from. +/// Each URL should return newline-separated peer addresses. This is similar +/// to the `--peer-list-url` flag in the native node, but allows multiple URLs +/// to be supplied. +/// * `seed_nodes_addresses` - Optional list of peer addresses to connect to +/// directly, in [WebRTC Multiaddr-ish format](https://o1-labs.github.io/mina-rust/docs/developers/webrtc#address-format-differences) +/// This is directly comparable to the `--peers` flag in the native node. +/// * `genesis_config_url` - Optional URL to fetch genesis configuration from. +/// Genesis config must be in bin_prot format. If not provided, uses the default +/// devnet configuration. +/// +/// # Returns +/// +/// An `RpcSender` that can be used to send RPC commands to the running node. +/// +/// # Panics +/// +/// Panics if: +/// - Block producer key parsing fails +/// - Node setup or build fails +/// - Genesis configuration cannot be fetched +/// +/// # Example +/// +/// ```javascript +/// const rpc = await run( +/// null, // No block production +/// ["https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt"], +/// ["/PEER_ID/https/webrtc-peer-signaling.example.com/443"], +/// null, // Use the default devnet configuration +/// ); +/// ``` #[wasm_bindgen] pub async fn run( block_producer: JsValue, - seed_nodes_url: Option, + seed_nodes_urls: Option>, + seed_nodes_addresses: Option>, genesis_config_url: Option, ) -> RpcSender { let block_producer = parse_bp_key(block_producer); @@ -85,7 +132,13 @@ pub async fn run( let (rpc_sender_tx, rpc_sender_rx) = ::node::core::channels::oneshot::channel(); let _ = thread::spawn(move || { wasm_bindgen_futures::spawn_local(async move { - let mut node = setup_node(block_producer, seed_nodes_url, genesis_config_url).await; + let mut node = setup_node( + block_producer, + seed_nodes_urls, + seed_nodes_addresses, + genesis_config_url, + ) + .await; let _ = rpc_sender_tx.send(node.rpc()); node.run_forever().await; }); @@ -98,7 +151,8 @@ pub async fn run( async fn setup_node( block_producer: Option, - seed_nodes_url: Option, + seed_nodes_urls: Option>, + seed_nodes_addresses: Option>, genesis_config_url: Option, ) -> mina_node_common::Node { let block_verifier_index = BlockVerifier::make().await; @@ -119,18 +173,32 @@ async fn setup_node( .work_verifier_index(work_verifier_index.clone()); // TODO(binier): refactor - if let Some(seed_nodes_url) = seed_nodes_url { - let peers = ::node::core::http::get_bytes(&seed_nodes_url) - .await - .expect("failed to fetch seed nodes"); - node_builder.initial_peers( - String::from_utf8_lossy(&peers) - .split("\n") - .filter(|s| !s.trim().is_empty()) - .map(|s| s.trim().parse().expect("failed to parse seed node addr")), - ); + let mut all_raw_peers = seed_nodes_addresses.unwrap_or_default(); + + if let Some(seed_nodes_urls) = seed_nodes_urls { + for seed_nodes_url in seed_nodes_urls { + let peers = ::node::core::http::get_bytes(&seed_nodes_url).await; + match peers { + Ok(s) => { + log::info!("Successfully fetched peers from {seed_nodes_url}"); + all_raw_peers.extend(String::from_utf8_lossy(&s).split("\n").map(String::from)); + } + Err(e) => { + log::error!("Failed to fetch peers from {seed_nodes_url}: {e}"); + } + } + } } + node_builder.initial_peers( + all_raw_peers + .iter() + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .flat_map(|s| s.parse().ok()) + .inspect(|p| log::debug!("Using peer: {p:?}")), + ); + if let Some(bp_key) = block_producer { thread::spawn(move || { BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); diff --git a/website/docs/developers/scripts/setup/install-wasm-tools.sh b/website/docs/developers/scripts/setup/install-wasm-tools.sh index db2f1c51b..1b87dacb1 100755 --- a/website/docs/developers/scripts/setup/install-wasm-tools.sh +++ b/website/docs/developers/scripts/setup/install-wasm-tools.sh @@ -2,7 +2,7 @@ cargo install wasm-pack # Install wasm-bindgen CLI tool for generating WebAssembly bindings -cargo install -f wasm-bindgen-cli --version 0.2.99 +cargo install --force wasm-bindgen-cli --version 0.2.106 --force # Add WebAssembly target rustup target add wasm32-unknown-unknown diff --git a/website/docs/developers/webrtc.md b/website/docs/developers/webrtc.md index dd6f09b8a..54c48b50e 100644 --- a/website/docs/developers/webrtc.md +++ b/website/docs/developers/webrtc.md @@ -181,6 +181,71 @@ The Web Node represents a significant advancement in blockchain accessibility, enabling truly decentralized participation without requiring users to install native applications or manage complex network configurations. +## Address Format Differences + +The Mina Rust Node uses a custom Multiaddr-like format for WebRTC peer addresses +that differs from normal Multiaddrs. These addresses are specifically designed +to encode signaling server information rather than direct network addresses, +reflecting the different connection model of WebRTC versus direct TCP/UDP +connections. + +The address parsing logic is implemented in +[`p2p/src/connection/outgoing/mod.rs`](https://github.com/o1-labs/mina-rust/blob/develop/p2p/src/connection/outgoing/mod.rs) +through the `FromStr` implementation for `P2pConnectionOutgoingInitOpts`. The +parser distinguishes between libp2p addresses (starting with `/ip` or `/dns`) +and WebRTC addresses (all other formats). + +### WebRTC peer address format + +WebRTC peer addresses follow this structure: + +``` +/{peer_id}/{signaling_method} +``` + +Where `{peer_id}` is the base58-encoded peer ID and `{signaling_method}` +specifies how to reach the signaling server. + +### Signaling method formats + +The signaling method component can take several forms: + +**HTTP signaling:** + +``` +/http/{host}/{port} +``` + +Example: +`/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK74R/http/localhost/8080` + +**HTTPS signaling:** + +``` +/https/{host}/{port} +``` + +Example: +`/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK74R/https/signal.example.com/443` + +**HTTPS proxy signaling:** + +``` +/https_proxy/{cluster_id}/{host}/{port} +``` + +Example: +`/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK74R/https_proxy/123/proxy.example.com/443` + +**P2P relay signaling:** + +``` +/p2p/{relay_peer_id} +``` + +Example: +`/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK74R/p2p/12D3KooWABC...` + ## Future Considerations While the current OCaml implementation doesn't use WebRTC, the Rust diff --git a/website/docs/node-operators/webnode/local-webnode-docker.md b/website/docs/node-operators/webnode/local-webnode-docker.md new file mode 100644 index 000000000..b0c10f56e --- /dev/null +++ b/website/docs/node-operators/webnode/local-webnode-docker.md @@ -0,0 +1,98 @@ +--- +sidebar_position: 1 +title: Local WebNode (Docker) +description: How to deploy and launch a webnode using Docker images +--- + +# Deploying and launching a webnode using Docker + +If you intend to just run the webnode instead of actively developing it, it is +substantially easier to get set up using prebuilt Docker images. These images +are automatically built on every versioned release. + +## Steps + +### 1. Generate a node key + +:::info This step should be redundant in a future version. + +::: + +The current version of the webnode requires you to supply a node key, even if +you don't plan to produce blocks, or you supply your own key archive. This can +be generated once and reused across launches of the webnode container. + +```sh +# Using the latest version of the Rust node, generate a keypair. +# The --web-node-secrets flag formats the generated keypair into JSON the webnode can use. +docker run --rm o1labs/mina-rust:latest misc mina-key-pair --web-node-secrets > $HOME/web-node-key-pair.json +``` + +### 2. Launch the webnode container + +You can now simply launch the webnode as a container. Note that once +pre-generating a node keypair is no longer necessary, the `-v` can be removed. + +```sh +# Launch the latest version of the frontend in webnode configuration. +# We mount the keypair generated in step 1, and bind port 4200 on the host to 80 (http) in the container +docker run \ + -e MINA_FRONTEND_ENVIRONMENT=webnode \ + -v ~/web-node-key-pair.json:/usr/local/apache2/htdocs/assets/webnode/web-node-secrets.json \ + -p 4200:80 \ + o1labs/mina-rust-frontend:latest +``` + +### 3. Open your browser + +Navigate to [http://localhost:4200](http://localhost:4200) and enjoy using the +webnode! If you used a different port (`-p`) when launching the container, then +update the port accordingly. + +## Environment Variable Reference + +The Dockerized WebNode can be configured with additional environment variables +to customize behavior. + +### `MINA_FRONTEND_ENVIRONMENT` + +This is required to launch the frontend in general. To serve a webnode, it must +be set to the value `webnode`. + +Example: + +``` +-e MINA_FRONTEND_ENVIRONMENT=webnode +``` + +### `MINA_WEBNODE_SEED_URLS` + +A comma-separated list of http(s) URLs to fetch initial P2P seeds from. This is +similar to the `--peer-list-url` flag in the native node, but can take multiple +comma-separated values to fetch peer lists from multiple sources. Each peer list +file must contain peer addresses in +[WebRTC-Multiaddrish format](../../developers/webrtc.md#address-format-differences), +separated by a newline. + +Example: + +``` +-e MINA_WEBNODE_SEED_URLS=https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt +-e MINA_WEBNODE_SEED_URLS=https://example.com/extra-peers.txt,https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt, +``` + +### `MINA_WEBNODE_BOOTNODES` + +A comma separated list of +[WebRTC-Multiaddrish](../../developers/webrtc.md#address-format-differences) +peer addresses, to attempt to connect to in conjunction to any downloaded from +`MINA_WEBNODE_SEED_URLS`. This is akin to the `--peers` flag in the native node, +but instead of specifying the flag multiple times, each peer is simply +comma-separated + +Example: + +``` +-e MINA_WEBNODE_BOOTNODES=/peer_id1/https/signaling.example.com/443 +-e MINA_WEBNODE_BOOTNODES=/peer_id1/https/signaling.example.com/443,/peer_id2/p2p/peer_id1 +``` diff --git a/website/sidebars.ts b/website/sidebars.ts index efdf3b296..7105e1837 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -61,6 +61,7 @@ const sidebars: SidebarsConfig = { items: [ 'node-operators/alpha-testing', 'node-operators/webnode/local-webnode', + 'node-operators/webnode/local-webnode-docker', 'node-operators/testing/overview', ], },