diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f61d658 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bun install + + - run: bun test/run.js + + lint-miner: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + + - name: Check miner compiles + working-directory: crates/salvium-miner + run: cargo check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6969959 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,176 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + # ── WASM artifacts (platform-independent) ──────────────────────────── + wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Install wasm-pack + run: cargo install wasm-pack + + - name: Install JS dependencies + run: bun install + + - name: Build WASM crypto + working-directory: crates/salvium-crypto + run: | + RUSTFLAGS="-Ctarget-feature=+simd128" \ + wasm-pack build --target web --out-dir ../../src/crypto/wasm + + - name: Build RandomX WASM + run: bun run build:wasm + + - name: Upload WASM artifacts + uses: actions/upload-artifact@v4 + with: + name: wasm-artifacts + path: | + src/crypto/wasm/salvium_crypto_bg.wasm + src/crypto/wasm/salvium_crypto.js + src/crypto/wasm/salvium_crypto.d.ts + build/randomx.wasm + + # ── Native miner binaries ──────────────────────────────────────────── + miner: + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-musl + os: ubuntu-latest + name: salvium-miner-linux-x86_64 + cross: true + + - target: aarch64-unknown-linux-musl + os: ubuntu-latest + name: salvium-miner-linux-aarch64 + cross: true + + - target: x86_64-apple-darwin + os: macos-13 + name: salvium-miner-macos-x86_64 + + - target: aarch64-apple-darwin + os: macos-latest + name: salvium-miner-macos-aarch64 + + - target: x86_64-pc-windows-msvc + os: windows-latest + name: salvium-miner-windows-x86_64.exe + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + # Linux: install cross for musl/cross-compilation + - name: Install cross (Linux) + if: matrix.cross + run: cargo install cross --git https://github.com/cross-rs/cross + + # macOS: install cmake + - name: Install cmake (macOS) + if: runner.os == 'macOS' + run: brew install cmake + + # Windows: install cmake + - name: Install cmake (Windows) + if: runner.os == 'Windows' + run: choco install cmake --installargs '"ADD_CMAKE_TO_PATH=System"' -y + + # Build with cross (Linux musl targets) + - name: Build with cross (Linux) + if: matrix.cross + working-directory: crates/salvium-miner + run: cross build --release --target ${{ matrix.target }} + + # Build natively (macOS / Windows) + - name: Build native (macOS/Windows) + if: ${{ !matrix.cross }} + working-directory: crates/salvium-miner + shell: bash + run: | + if [ "$RUNNER_OS" = "Windows" ]; then + export RUSTFLAGS="-C target-feature=+crt-static" + fi + cargo build --release --target ${{ matrix.target }} + + # Rename binary to target name + - name: Rename binary (Unix) + if: runner.os != 'Windows' + run: | + cp crates/salvium-miner/target/${{ matrix.target }}/release/salvium-miner \ + ${{ matrix.name }} + + - name: Rename binary (Windows) + if: runner.os == 'Windows' + run: | + copy crates\salvium-miner\target\${{ matrix.target }}\release\salvium-miner.exe ^ + ${{ matrix.name }} + shell: cmd + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: ${{ matrix.name }} + + # ── Create GitHub Release ──────────────────────────────────────────── + release: + needs: [wasm, miner] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release files + run: | + mkdir -p release + + # Miner binaries + for dir in artifacts/salvium-miner-*; do + name=$(basename "$dir") + cp "$dir/$name" "release/$name" + chmod +x "release/$name" 2>/dev/null || true + done + + # WASM files + cp artifacts/wasm-artifacts/src/crypto/wasm/salvium_crypto_bg.wasm release/ + cp artifacts/wasm-artifacts/build/randomx.wasm release/ + + # Generate checksums + cd release + sha256sum * > checksums.sha256 + cat checksums.sha256 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: release/* diff --git a/crates/salvium-miner/Cargo.lock b/crates/salvium-miner/Cargo.lock index 876cdd8..b631bcc 100644 --- a/crates/salvium-miner/Cargo.lock +++ b/crates/salvium-miner/Cargo.lock @@ -104,6 +104,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.5.56" @@ -159,22 +165,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "displaydoc" version = "0.2.5" @@ -186,64 +176,12 @@ dependencies = [ "syn", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -310,8 +248,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -321,36 +261,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "heck" version = "0.5.0" @@ -418,7 +335,6 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -444,22 +360,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "webpki-roots", ] [[package]] @@ -481,11 +382,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -590,16 +489,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -644,12 +533,6 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - [[package]] name = "litemap" version = "0.8.1" @@ -662,18 +545,18 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "mio" version = "1.1.1" @@ -685,23 +568,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "num_cpus" version = "1.17.0" @@ -724,50 +590,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -786,12 +608,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "potential_utf" version = "0.1.4" @@ -801,6 +617,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -810,6 +635,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.44" @@ -825,6 +705,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "randomx-rs" version = "1.4.1" @@ -834,7 +743,7 @@ dependencies = [ "bitflags 1.3.2", "cmake", "libc", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -845,31 +754,28 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -877,6 +783,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", ] [[package]] @@ -894,17 +801,10 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "1.1.3" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags 2.10.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustls" @@ -913,6 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -925,6 +826,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] @@ -965,38 +867,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.10.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.228" @@ -1129,47 +999,22 @@ dependencies = [ "syn", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.10.0", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1183,6 +1028,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -1193,6 +1049,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -1207,16 +1078,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -1227,19 +1088,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "tower" version = "0.5.3" @@ -1346,12 +1194,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "want" version = "0.3.1" @@ -1445,41 +1287,31 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -1671,6 +1503,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/crates/salvium-miner/Cargo.toml b/crates/salvium-miner/Cargo.toml index 2b997cb..7177590 100644 --- a/crates/salvium-miner/Cargo.toml +++ b/crates/salvium-miner/Cargo.toml @@ -10,7 +10,7 @@ path = "src/main.rs" [dependencies] randomx-rs = "1.4" -reqwest = { version = "0.12", features = ["blocking", "json"] } +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1", features = ["derive"] } serde_json = "1" clap = { version = "4", features = ["derive"] } diff --git a/package.json b/package.json index e6ce4f7..ec95251 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "./randomx": "./src/randomx/index.js" }, "scripts": { - "test": "node test/run.js", + "setup": "./setup.sh", + "test": "bun test/run.js", "build:wasm": "asc assembly/index.ts --outFile build/randomx.wasm --optimize --enable simd --initialMemory 16 --maximumMemory 8192", "build:wasm-crypto": "cd crates/salvium-crypto && PATH=\"$HOME/.cargo/bin:$HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin:$PATH\" RUSTUP_HOME=\"$HOME/.rustup\" CARGO_HOME=\"$HOME/.cargo\" RUSTFLAGS=\"-Ctarget-feature=+simd128\" wasm-pack build --target web --out-dir ../../src/crypto/wasm", "build:miner": "cd crates/salvium-miner && PATH=\"$HOME/.cargo/bin:$HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin:$PATH\" RUSTUP_HOME=\"$HOME/.rustup\" CARGO_HOME=\"$HOME/.cargo\" cargo build --release", @@ -74,7 +75,7 @@ "url": "https://github.com/whiskymine/salvium-js" }, "engines": { - "node": ">=16.0.0" + "bun": ">=1.0.0" }, "browser": { "./src/keccak.js": "./src/keccak.browser.js" diff --git a/setup.bat b/setup.bat new file mode 100644 index 0000000..023699c --- /dev/null +++ b/setup.bat @@ -0,0 +1,309 @@ +@echo off +setlocal enabledelayedexpansion + +REM ══════════════════════════════════════════════════════════════════════ +REM salvium-js build setup (Windows) +REM +REM Detects dependencies, provides install instructions, builds all +REM components when ready. +REM +REM Usage: +REM setup.bat Build everything +REM setup.bat --check Check dependencies only +REM setup.bat --js-only JS library only (bun install) +REM setup.bat --no-miner Build WASM components, skip native miner +REM ══════════════════════════════════════════════════════════════════════ + +set CHECK_ONLY=0 +set JS_ONLY=0 +set NO_MINER=0 +set MISSING_COUNT=0 + +for %%A in (%*) do ( + if "%%A"=="--check" set CHECK_ONLY=1 + if "%%A"=="--js-only" set JS_ONLY=1 + if "%%A"=="--no-miner" set NO_MINER=1 + if "%%A"=="--help" goto :usage + if "%%A"=="-h" goto :usage +) + +goto :start + +:usage +echo Usage: setup.bat [--check] [--js-only] [--no-miner] +echo. +echo --check Check dependencies only, don't build +echo --js-only Only install JS dependencies (skip WASM and native builds) +echo --no-miner Build WASM components but skip native miner +exit /b 0 + +:start +echo. +echo salvium-js build setup (Windows) +echo ================================ +echo. + +REM ── Detect architecture ─────────────────────────────────────────────── + +set ARCH=%PROCESSOR_ARCHITECTURE% +echo Platform: Windows %ARCH% +echo. + +REM ── Check Bun (required) ────────────────────────────────────────────── + +echo Required: Runtime +echo ----------------- + +where bun >nul 2>&1 +if %errorlevel%==0 ( + for /f "tokens=*" %%V in ('bun --version 2^>nul') do set BUN_VER=%%V + echo [OK] bun !BUN_VER! +) else ( + echo [MISS] bun -- powershell -c "irm bun.sh/install.ps1 | iex" + set /a MISSING_COUNT+=1 + echo. + echo Bun is required. Install it first: + echo powershell -c "irm bun.sh/install.ps1 | iex" + echo. + if %CHECK_ONLY%==1 exit /b 1 + echo Cannot continue without bun. + exit /b 1 +) + +REM ── Check Rust toolchain ────────────────────────────────────────────── + +if %JS_ONLY%==1 goto :skip_rust + +echo. +echo Required: Rust toolchain +echo ------------------------ + +where rustc >nul 2>&1 +if %errorlevel%==0 ( + for /f "tokens=*" %%V in ('rustc --version 2^>nul') do set RUSTC_VER=%%V + echo [OK] !RUSTC_VER! +) else ( + echo [MISS] rustc -- winget install Rustlang.Rustup (or https://rustup.rs) + set /a MISSING_COUNT+=1 +) + +where cargo >nul 2>&1 +if %errorlevel%==0 ( + echo [OK] cargo +) else ( + echo [MISS] cargo -- (installed with rustup) + set /a MISSING_COUNT+=1 +) + +REM Check wasm32 target +where rustup >nul 2>&1 +if %errorlevel%==0 ( + rustup target list --installed 2>nul | findstr /c:"wasm32-unknown-unknown" >nul 2>&1 + if !errorlevel!==0 ( + echo [OK] wasm32-unknown-unknown target + ) else ( + echo [MISS] wasm32-unknown-unknown target -- rustup target add wasm32-unknown-unknown + set /a MISSING_COUNT+=1 + ) +) + +where wasm-pack >nul 2>&1 +if %errorlevel%==0 ( + echo [OK] wasm-pack +) else ( + echo [MISS] wasm-pack -- cargo install wasm-pack + set /a MISSING_COUNT+=1 +) + +:skip_rust + +REM ── Check C/C++ toolchain ───────────────────────────────────────────── + +if %JS_ONLY%==1 goto :skip_cxx +if %NO_MINER%==1 goto :skip_cxx + +echo. +echo Required: C/C++ toolchain (for native miner) +echo --------------------------------------------- + +set HAS_CXX=0 + +where cl >nul 2>&1 +if %errorlevel%==0 ( + echo [OK] MSVC cl.exe + set HAS_CXX=1 +) + +if %HAS_CXX%==0 ( + where g++ >nul 2>&1 + if !errorlevel!==0 ( + echo [OK] g++ + set HAS_CXX=1 + ) +) + +if %HAS_CXX%==0 ( + where clang++ >nul 2>&1 + if !errorlevel!==0 ( + echo [OK] clang++ + set HAS_CXX=1 + ) +) + +if %HAS_CXX%==0 ( + echo [MISS] C++ compiler -- winget install Microsoft.VisualStudio.2022.BuildTools + echo (select "Desktop development with C++" workload) + set /a MISSING_COUNT+=1 +) + +where cmake >nul 2>&1 +if %errorlevel%==0 ( + for /f "tokens=*" %%V in ('cmake --version 2^>nul ^| findstr /n "." ^| findstr "^1:"') do set CMAKE_VER=%%V + echo [OK] cmake +) else ( + echo [MISS] cmake -- winget install Kitware.CMake + set /a MISSING_COUNT+=1 +) + +:skip_cxx + +REM ── Summary ─────────────────────────────────────────────────────────── + +echo. +if %MISSING_COUNT% gtr 0 ( + echo Missing dependencies: %MISSING_COUNT% + echo Install the tools listed above, then re-run setup.bat + echo. + if %CHECK_ONLY%==1 exit /b 1 + echo Some components may fail to build. Continuing with what's available... + echo. +) else ( + echo All dependencies satisfied. + echo. +) + +if %CHECK_ONLY%==1 exit /b 0 + +REM ── Build ───────────────────────────────────────────────────────────── + +set BUILT=0 +set BUILD_FAILED=0 + +REM 1. Install JS dependencies +echo. +echo Installing JS dependencies... +echo ----------------------------- +call bun install +if %errorlevel%==0 ( + echo [OK] JS dependencies + set /a BUILT+=1 +) else ( + echo [FAIL] JS dependencies + set /a BUILD_FAILED+=1 +) + +if %JS_ONLY%==1 goto :results + +REM 2. Build WASM crypto +where wasm-pack >nul 2>&1 +if %errorlevel%==0 ( + where cargo >nul 2>&1 + if !errorlevel!==0 ( + echo. + echo Building WASM crypto... + echo ----------------------- + pushd crates\salvium-crypto + set RUSTFLAGS=-Ctarget-feature=+simd128 + call wasm-pack build --target web --out-dir ../../src/crypto/wasm + if !errorlevel!==0 ( + echo [OK] WASM crypto + set /a BUILT+=1 + ) else ( + echo [FAIL] WASM crypto + set /a BUILD_FAILED+=1 + ) + popd + ) +) else ( + echo [SKIP] WASM crypto (missing wasm-pack or cargo) + set /a BUILD_FAILED+=1 +) + +REM 3. Build RandomX WASM +echo. +echo Building RandomX WASM... +echo ------------------------ +call bun run build:wasm +if %errorlevel%==0 ( + echo [OK] RandomX WASM + set /a BUILT+=1 +) else ( + echo [FAIL] RandomX WASM + set /a BUILD_FAILED+=1 +) + +REM 4. Build native miner +if %NO_MINER%==1 goto :results + +where cargo >nul 2>&1 +if %errorlevel%==0 ( + where cmake >nul 2>&1 + if !errorlevel!==0 ( + echo. + echo Building native miner... + echo ------------------------ + pushd crates\salvium-miner + call cargo build --release + if !errorlevel!==0 ( + echo [OK] Native miner + set /a BUILT+=1 + ) else ( + echo [FAIL] Native miner + set /a BUILD_FAILED+=1 + ) + popd + ) else ( + echo [SKIP] Native miner (missing cmake) + set /a BUILD_FAILED+=1 + ) +) else ( + echo [SKIP] Native miner (missing cargo) + set /a BUILD_FAILED+=1 +) + +:results + +REM ── Results ─────────────────────────────────────────────────────────── + +echo. +echo Build Results +echo ============= +echo. +echo Built: %BUILT% Failed/Skipped: %BUILD_FAILED% +echo. + +if exist src\crypto\wasm\salvium_crypto_bg.wasm ( + echo WASM crypto: src\crypto\wasm\salvium_crypto_bg.wasm +) +if exist build\randomx.wasm ( + echo RandomX WASM: build\randomx.wasm +) +if exist crates\salvium-miner\target\release\salvium-miner.exe ( + echo Native miner: crates\salvium-miner\target\release\salvium-miner.exe +) + +echo. +if %BUILD_FAILED%==0 ( + echo All components built successfully. + echo. + echo Quick start: + echo bun test\run.js -- Run tests + if exist crates\salvium-miner\target\release\salvium-miner.exe ( + echo crates\salvium-miner\target\release\salvium-miner.exe --benchmark -- Benchmark miner + ) +) else ( + echo Some components failed. Check output above. +) + +echo. +exit /b %BUILD_FAILED% diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..21ffdd7 --- /dev/null +++ b/setup.sh @@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# +# salvium-js build setup +# +# Detects platform, checks dependencies, provides install instructions, +# and builds all components when ready. +# +# Usage: +# ./setup.sh Build everything +# ./setup.sh --check Check dependencies only +# ./setup.sh --js-only JS library only (bun install) +# ./setup.sh --no-miner Build WASM components, skip native miner +# + +set -euo pipefail + +# ── Colors ────────────────────────────────────────────────────────────── + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +ok() { printf " ${GREEN}[OK]${NC} %s\n" "$1"; } +miss() { printf " ${RED}[MISS]${NC} %s\n" "$1"; } +warn() { printf " ${YELLOW}[WARN]${NC} %s\n" "$1"; } +info() { printf "${CYAN}%s${NC}\n" "$1"; } +header() { printf "\n${BOLD}%s${NC}\n" "$1"; } + +# ── Parse args ────────────────────────────────────────────────────────── + +CHECK_ONLY=false +JS_ONLY=false +NO_MINER=false + +for arg in "$@"; do + case "$arg" in + --check) CHECK_ONLY=true ;; + --js-only) JS_ONLY=true ;; + --no-miner) NO_MINER=true ;; + --help|-h) + echo "Usage: ./setup.sh [--check] [--js-only] [--no-miner]" + echo "" + echo " --check Check dependencies only, don't build" + echo " --js-only Only install JS dependencies (skip WASM and native builds)" + echo " --no-miner Build WASM components but skip native miner" + exit 0 + ;; + *) echo "Unknown option: $arg"; exit 1 ;; + esac +done + +# ── Detect platform ──────────────────────────────────────────────────── + +# Ensure Rust/Cargo bin dirs are on PATH +[ -d "$HOME/.cargo/bin" ] && export PATH="$HOME/.cargo/bin:$PATH" +[ -d "$HOME/.rustup/toolchains" ] && { + for tc in "$HOME"/.rustup/toolchains/*/bin; do + [ -d "$tc" ] && export PATH="$tc:$PATH" + done +} + +OS="$(uname -s)" +ARCH="$(uname -m)" +PKG_MGR="" + +case "$OS" in + Linux) + if command -v apt-get &>/dev/null; then PKG_MGR="apt" + elif command -v dnf &>/dev/null; then PKG_MGR="dnf" + elif command -v pacman &>/dev/null; then PKG_MGR="pacman" + elif command -v apk &>/dev/null; then PKG_MGR="apk" + elif command -v zypper &>/dev/null; then PKG_MGR="zypper" + fi + ;; + Darwin) PKG_MGR="brew" ;; + FreeBSD) PKG_MGR="pkg" ;; +esac + +header "salvium-js build setup" +echo "" +info "Platform: $OS $ARCH" +[ -n "$PKG_MGR" ] && info "Package manager: $PKG_MGR" +echo "" + +# ── Dependency checks ────────────────────────────────────────────────── + +MISSING=() +INSTALL_CMDS=() + +check_cmd() { + local name="$1" + local cmd="$2" + local install_hint="$3" + if command -v "$cmd" &>/dev/null; then + local ver + ver=$("$cmd" --version 2>/dev/null | head -1 || echo "found") + ok "$name ($ver)" + return 0 + else + miss "$name -- $install_hint" + MISSING+=("$name") + INSTALL_CMDS+=("$install_hint") + return 1 + fi +} + +# ── Bun (required) ───────────────────────────────────────────────────── + +header "Required: Runtime" + +check_cmd "bun" "bun" "curl -fsSL https://bun.sh/install | bash" || true + +if ! command -v bun &>/dev/null; then + echo "" + printf "${RED}Bun is required. Install it first:${NC}\n" + echo " curl -fsSL https://bun.sh/install | bash" + echo "" + if $CHECK_ONLY; then + exit 1 + fi + echo "Cannot continue without bun." + exit 1 +fi + +# ── Rust toolchain (needed for WASM crypto + miner) ──────────────────── + +if ! $JS_ONLY; then + header "Required: Rust toolchain" + + check_cmd "rustc" "rustc" "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" || true + check_cmd "cargo" "cargo" "(installed with rustup)" || true + + # Check wasm32 target + if command -v rustup &>/dev/null; then + if rustup target list --installed 2>/dev/null | grep -q wasm32-unknown-unknown; then + ok "wasm32-unknown-unknown target" + else + miss "wasm32-unknown-unknown target -- rustup target add wasm32-unknown-unknown" + MISSING+=("wasm32-target") + INSTALL_CMDS+=("rustup target add wasm32-unknown-unknown") + fi + fi + + check_cmd "wasm-pack" "wasm-pack" "cargo install wasm-pack" || true +fi + +# ── C/C++ toolchain (needed for native miner) ────────────────────────── + +if ! $JS_ONLY && ! $NO_MINER; then + header "Required: C/C++ toolchain (for native miner)" + + HAS_CXX=false + if command -v g++ &>/dev/null; then + ok "g++ ($(g++ --version | head -1))" + HAS_CXX=true + elif command -v clang++ &>/dev/null; then + ok "clang++ ($(clang++ --version | head -1))" + HAS_CXX=true + elif command -v c++ &>/dev/null; then + ok "c++ compiler found" + HAS_CXX=true + fi + + if ! $HAS_CXX; then + case "$PKG_MGR" in + apt) hint="sudo apt install build-essential" ;; + dnf) hint="sudo dnf groupinstall 'Development Tools'" ;; + pacman) hint="sudo pacman -S base-devel" ;; + brew) hint="xcode-select --install" ;; + pkg) hint="sudo pkg install gcc" ;; + *) hint="install gcc or clang" ;; + esac + miss "C++ compiler -- $hint" + MISSING+=("c++") + INSTALL_CMDS+=("$hint") + fi + + # cmake + case "$PKG_MGR" in + apt) cmake_hint="sudo apt install cmake" ;; + dnf) cmake_hint="sudo dnf install cmake" ;; + pacman) cmake_hint="sudo pacman -S cmake" ;; + brew) cmake_hint="brew install cmake" ;; + pkg) cmake_hint="sudo pkg install cmake" ;; + *) cmake_hint="install cmake (https://cmake.org)" ;; + esac + check_cmd "cmake" "cmake" "$cmake_hint" || true + + # make + if ! command -v make &>/dev/null && ! command -v ninja &>/dev/null; then + case "$PKG_MGR" in + apt) hint="sudo apt install make" ;; + dnf) hint="sudo dnf install make" ;; + brew) hint="(included with xcode-select --install)" ;; + *) hint="install make or ninja" ;; + esac + miss "make/ninja -- $hint" + MISSING+=("make") + INSTALL_CMDS+=("$hint") + else + if command -v make &>/dev/null; then + ok "make" + else + ok "ninja" + fi + fi +fi + +# ── Summary ───────────────────────────────────────────────────────────── + +echo "" +if [ ${#MISSING[@]} -gt 0 ]; then + header "Missing dependencies (${#MISSING[@]}):" + echo "" + printf " Install commands:\n" + # Deduplicate + printf '%s\n' "${INSTALL_CMDS[@]}" | sort -u | while read -r cmd; do + printf " ${YELLOW}%s${NC}\n" "$cmd" + done + echo "" + if $CHECK_ONLY; then + echo "Run the install commands above, then re-run ./setup.sh" + exit 1 + fi + echo "Some components may fail to build. Continuing with what's available..." + echo "" +else + printf "${GREEN}All dependencies satisfied.${NC}\n\n" +fi + +if $CHECK_ONLY; then + exit 0 +fi + +# ── Build ─────────────────────────────────────────────────────────────── + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +BUILT=() +FAILED=() + +# 1. Install JS dependencies +header "Installing JS dependencies..." +if bun install; then + BUILT+=("JS dependencies") +else + FAILED+=("JS dependencies") +fi + +if $JS_ONLY; then + header "Build complete (JS only)" + exit 0 +fi + +# 2. Build WASM crypto +if command -v wasm-pack &>/dev/null && command -v cargo &>/dev/null; then + header "Building WASM crypto..." + ( + cd crates/salvium-crypto + RUSTFLAGS="-Ctarget-feature=+simd128" wasm-pack build --target web --out-dir ../../src/crypto/wasm + ) && BUILT+=("WASM crypto") || FAILED+=("WASM crypto") +else + warn "Skipping WASM crypto (missing wasm-pack or cargo)" + FAILED+=("WASM crypto (skipped)") +fi + +# 3. Build RandomX WASM +header "Building RandomX WASM..." +if bun run build:wasm; then + BUILT+=("RandomX WASM") +else + FAILED+=("RandomX WASM") +fi + +# 4. Build native miner +if ! $NO_MINER; then + if command -v cargo &>/dev/null && command -v cmake &>/dev/null; then + header "Building native miner (this may take a few minutes on first build)..." + ( + cd crates/salvium-miner + cargo build --release + ) && BUILT+=("Native miner") || FAILED+=("Native miner") + else + warn "Skipping native miner (missing cargo or cmake)" + FAILED+=("Native miner (skipped)") + fi +fi + +# ── Results ───────────────────────────────────────────────────────────── + +echo "" +header "Build Results" +echo "" + +for item in "${BUILT[@]}"; do + ok "$item" +done +for item in "${FAILED[@]}"; do + miss "$item" +done + +echo "" +if [ ${#BUILT[@]} -gt 0 ]; then + header "Output locations:" + [ -f src/crypto/wasm/salvium_crypto_bg.wasm ] && echo " WASM crypto: src/crypto/wasm/salvium_crypto_bg.wasm" + [ -f build/randomx.wasm ] && echo " RandomX WASM: build/randomx.wasm" + MINER_BIN="crates/salvium-miner/target/release/salvium-miner" + [ -f "$MINER_BIN" ] && echo " Native miner: $MINER_BIN" + echo "" +fi + +if [ ${#FAILED[@]} -eq 0 ]; then + printf "${GREEN}All components built successfully.${NC}\n" + echo "" + echo "Quick start:" + echo " bun test/run.js # Run tests" + if [ -f "crates/salvium-miner/target/release/salvium-miner" ]; then + echo " ./crates/salvium-miner/target/release/salvium-miner --benchmark # Benchmark miner" + fi + exit 0 +else + printf "${YELLOW}Some components failed. Check output above.${NC}\n" + exit 1 +fi diff --git a/src/rpc/client.js b/src/rpc/client.js index 7e31c90..87624cc 100644 --- a/src/rpc/client.js +++ b/src/rpc/client.js @@ -313,6 +313,75 @@ export class RPCClient { }; } + /** + * POST binary data (portable storage format) and parse binary response + * @param {string} endpoint - API endpoint path + * @param {Uint8Array} body - Binary request body + * @returns {Promise} + */ + async postBinary(endpoint, body) { + let lastError = null; + const attempts = this.retries + 1; + + for (let attempt = 1; attempt <= attempts; attempt++) { + try { + const url = endpoint.startsWith('/') + ? `${this.url}${endpoint}` + : `${this.url}/${endpoint}`; + + const response = await this._fetchWithTimeout(url, { + method: 'POST', + headers: { + ...this._buildHeaders(), + 'Content-Type': 'application/octet-stream' + }, + body + }); + + if (!response.ok) { + if (response.status === 401) { + return { + success: false, + error: { + code: RPC_ERROR_CODES.AUTHENTICATION_ERROR, + message: 'Authentication failed' + } + }; + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const arrayBuffer = await response.arrayBuffer(); + return { + success: true, + result: new Uint8Array(arrayBuffer) + }; + + } catch (error) { + if (error.name === 'AbortError') { + lastError = { + code: RPC_ERROR_CODES.TIMEOUT_ERROR, + message: `Request timed out after ${this.timeout}ms` + }; + } else { + lastError = { + code: RPC_ERROR_CODES.NETWORK_ERROR, + message: error.message || 'Network error' + }; + } + + if (attempt < attempts) { + await this._sleep(this.retryDelay); + } + } + } + + return { + success: false, + error: lastError + }; + } + /** * Make a raw HTTP GET request * @param {string} endpoint - API endpoint path diff --git a/src/rpc/daemon.js b/src/rpc/daemon.js index a242c06..1da9e04 100644 --- a/src/rpc/daemon.js +++ b/src/rpc/daemon.js @@ -14,6 +14,7 @@ */ import { RPCClient, RPC_STATUS } from './client.js'; +import { serialize as psSerialize, deserialize as psDeserialize } from './portable-storage.js'; /** * @typedef {import('./client.js').RPCClientOptions} RPCClientOptions @@ -278,10 +279,37 @@ export class DaemonRPC extends RPCClient { /** * Get blocks by height (efficient binary format) * @param {number[]} heights - Array of block heights - * @returns {Promise} Blocks data + * @returns {Promise} Blocks data with block and tx blobs */ async getBlocksByHeight(heights) { - return this.post('/get_blocks_by_height.bin', { heights }); + const body = psSerialize({ + heights: { _type: 'uint64_array', values: heights } + }); + const resp = await this.postBinary('/get_blocks_by_height.bin', body); + if (!resp.success) return resp; + + try { + const parsed = psDeserialize(resp.result); + // parsed.blocks is array of { block: Uint8Array, txs: Uint8Array[] } + // parsed.status is Uint8Array + const status = parsed.status + ? new TextDecoder().decode(parsed.status) + : 'OK'; + + if (status !== 'OK') { + return { success: false, error: { code: -1, message: `Status: ${status}` } }; + } + + return { + success: true, + result: { + blocks: parsed.blocks || [], + status + } + }; + } catch (e) { + return { success: false, error: { code: -1, message: e.message } }; + } } /** diff --git a/src/rpc/portable-storage.js b/src/rpc/portable-storage.js new file mode 100644 index 0000000..0e7106f --- /dev/null +++ b/src/rpc/portable-storage.js @@ -0,0 +1,387 @@ +/** + * Epee Portable Storage Binary Format + * + * Implements Monero/Salvium's binary serialization format used by .bin RPC endpoints. + * Format: signature (8 bytes) + root section (varint entry count + entries). + * + * Reference: epee/include/storages/portable_storage_{to,from}_bin.h + */ + +const SIGNATURE_A = 0x01011101; +const SIGNATURE_B = 0x01020101; +const FORMAT_VER = 1; + +// Type tags +const TYPE_INT64 = 1; +const TYPE_INT32 = 2; +const TYPE_INT16 = 3; +const TYPE_INT8 = 4; +const TYPE_UINT64 = 5; +const TYPE_UINT32 = 6; +const TYPE_UINT16 = 7; +const TYPE_UINT8 = 8; +const TYPE_DOUBLE = 9; +const TYPE_STRING = 10; +const TYPE_BOOL = 11; +const TYPE_OBJECT = 12; +const TYPE_ARRAY = 13; +const FLAG_ARRAY = 0x80; + +// Varint size masks +const SIZE_MARK_MASK = 0x03; +const SIZE_MARK_BYTE = 0; +const SIZE_MARK_WORD = 1; +const SIZE_MARK_DWORD = 2; +const SIZE_MARK_INT64 = 3; + +// ============================================================ +// WRITER +// ============================================================ + +class BinaryWriter { + constructor() { + this.chunks = []; + this.length = 0; + } + + writeUint8(v) { + const b = new Uint8Array(1); + b[0] = v & 0xFF; + this.chunks.push(b); + this.length += 1; + } + + writeUint32LE(v) { + const b = new Uint8Array(4); + new DataView(b.buffer).setUint32(0, v, true); + this.chunks.push(b); + this.length += 4; + } + + writeUint64LE(v) { + const b = new Uint8Array(8); + const dv = new DataView(b.buffer); + const n = BigInt(v); + dv.setUint32(0, Number(n & 0xFFFFFFFFn), true); + dv.setUint32(4, Number((n >> 32n) & 0xFFFFFFFFn), true); + this.chunks.push(b); + this.length += 8; + } + + writeBytes(buf) { + const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf); + this.chunks.push(u8); + this.length += u8.length; + } + + writeVarint(val) { + if (val <= 63) { + this.writeUint8((val << 2) | SIZE_MARK_BYTE); + } else if (val <= 16383) { + const v = (val << 2) | SIZE_MARK_WORD; + const b = new Uint8Array(2); + new DataView(b.buffer).setUint16(0, v, true); + this.chunks.push(b); + this.length += 2; + } else if (val <= 1073741823) { + const v = (val << 2) | SIZE_MARK_DWORD; + const b = new Uint8Array(4); + new DataView(b.buffer).setUint32(0, v, true); + this.chunks.push(b); + this.length += 4; + } else { + const v = BigInt(val) << 2n | BigInt(SIZE_MARK_INT64); + const b = new Uint8Array(8); + const dv = new DataView(b.buffer); + dv.setUint32(0, Number(v & 0xFFFFFFFFn), true); + dv.setUint32(4, Number((v >> 32n) & 0xFFFFFFFFn), true); + this.chunks.push(b); + this.length += 8; + } + } + + writeString(s) { + const encoded = typeof s === 'string' ? new TextEncoder().encode(s) : s; + this.writeVarint(encoded.length); + this.writeBytes(encoded); + } + + toBuffer() { + const result = new Uint8Array(this.length); + let offset = 0; + for (const chunk of this.chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + return result; + } +} + +/** + * Serialize a JS object to portable storage binary format. + * Supports: strings, numbers (uint64), booleans, arrays of uint64, arrays of strings, + * arrays of objects, and nested objects. + * + * For typed arrays, wrap values: { _type: 'uint64_array', values: [...] } + */ +export function serialize(obj) { + const w = new BinaryWriter(); + + // Header: sig_a (4) + sig_b (4) + ver (1) + w.writeUint32LE(SIGNATURE_A); + w.writeUint32LE(SIGNATURE_B); + w.writeUint8(FORMAT_VER); + + writeSection(w, obj); + return w.toBuffer(); +} + +function writeSection(w, obj) { + const keys = Object.keys(obj); + w.writeVarint(keys.length); + for (const key of keys) { + // Key: length (1 byte) + chars + const keyBytes = new TextEncoder().encode(key); + w.writeUint8(keyBytes.length); + w.writeBytes(keyBytes); + // Value + writeEntry(w, obj[key]); + } +} + +function writeEntry(w, val) { + if (val === null || val === undefined) { + // Write as empty string + w.writeUint8(TYPE_STRING); + w.writeVarint(0); + return; + } + + // Typed wrapper: { _type: 'uint64_array', values: [...] } + if (val && val._type === 'uint64_array') { + w.writeUint8(TYPE_UINT64 | FLAG_ARRAY); + w.writeVarint(val.values.length); + for (const v of val.values) { + w.writeUint64LE(v); + } + return; + } + + if (typeof val === 'boolean') { + w.writeUint8(TYPE_BOOL); + w.writeUint8(val ? 1 : 0); + } else if (typeof val === 'number') { + // Default to uint64 for numbers + w.writeUint8(TYPE_UINT64); + w.writeUint64LE(val); + } else if (typeof val === 'bigint') { + w.writeUint8(TYPE_UINT64); + w.writeUint64LE(val); + } else if (typeof val === 'string') { + w.writeUint8(TYPE_STRING); + w.writeString(val); + } else if (val instanceof Uint8Array || val instanceof ArrayBuffer) { + // Binary data as string + w.writeUint8(TYPE_STRING); + const u8 = val instanceof Uint8Array ? val : new Uint8Array(val); + w.writeVarint(u8.length); + w.writeBytes(u8); + } else if (Array.isArray(val)) { + if (val.length === 0) { + // Empty array — write as string array + w.writeUint8(TYPE_STRING | FLAG_ARRAY); + w.writeVarint(0); + } else if (typeof val[0] === 'object' && val[0] !== null) { + // Array of objects + w.writeUint8(TYPE_OBJECT | FLAG_ARRAY); + w.writeVarint(val.length); + for (const item of val) writeSection(w, item); + } else if (typeof val[0] === 'string') { + w.writeUint8(TYPE_STRING | FLAG_ARRAY); + w.writeVarint(val.length); + for (const s of val) w.writeString(s); + } else { + // Array of numbers → uint64 + w.writeUint8(TYPE_UINT64 | FLAG_ARRAY); + w.writeVarint(val.length); + for (const n of val) w.writeUint64LE(n); + } + } else if (typeof val === 'object') { + w.writeUint8(TYPE_OBJECT); + writeSection(w, val); + } +} + +// ============================================================ +// READER +// ============================================================ + +class BinaryReader { + constructor(buf) { + this.buf = buf instanceof Uint8Array ? buf : new Uint8Array(buf); + this.dv = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength); + this.pos = 0; + } + + readUint8() { + return this.buf[this.pos++]; + } + + readUint16LE() { + const v = this.dv.getUint16(this.pos, true); + this.pos += 2; + return v; + } + + readUint32LE() { + const v = this.dv.getUint32(this.pos, true); + this.pos += 4; + return v; + } + + readUint64LE() { + const lo = this.dv.getUint32(this.pos, true); + const hi = this.dv.getUint32(this.pos + 4, true); + this.pos += 8; + // Return as Number if safe, else BigInt + const n = BigInt(hi) << 32n | BigInt(lo); + if (n <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(n); + return n; + } + + readInt64LE() { + const lo = this.dv.getUint32(this.pos, true); + const hi = this.dv.getInt32(this.pos + 4, true); + this.pos += 8; + const n = BigInt(hi) << 32n | BigInt(lo); + if (n >= BigInt(Number.MIN_SAFE_INTEGER) && n <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(n); + return n; + } + + readInt32LE() { + const v = this.dv.getInt32(this.pos, true); + this.pos += 4; + return v; + } + + readInt16LE() { + const v = this.dv.getInt16(this.pos, true); + this.pos += 2; + return v; + } + + readDouble() { + const v = this.dv.getFloat64(this.pos, true); + this.pos += 8; + return v; + } + + readVarint() { + const mark = this.buf[this.pos] & SIZE_MARK_MASK; + switch (mark) { + case SIZE_MARK_BYTE: { + const v = this.readUint8(); + return v >> 2; + } + case SIZE_MARK_WORD: { + const v = this.readUint16LE(); + return v >> 2; + } + case SIZE_MARK_DWORD: { + const v = this.readUint32LE(); + return v >>> 2; + } + case SIZE_MARK_INT64: { + const lo = this.dv.getUint32(this.pos, true); + const hi = this.dv.getUint32(this.pos + 4, true); + this.pos += 8; + return Number((BigInt(hi) << 32n | BigInt(lo)) >> 2n); + } + } + } + + readString() { + const len = this.readVarint(); + const bytes = this.buf.slice(this.pos, this.pos + len); + this.pos += len; + return bytes; + } + + readBytes(n) { + const b = this.buf.slice(this.pos, this.pos + n); + this.pos += n; + return b; + } +} + +/** + * Deserialize portable storage binary format to a JS object. + * String fields are returned as Uint8Array (binary blobs). + * Use .toString() or TextDecoder for text strings. + */ +export function deserialize(buf) { + const r = new BinaryReader(buf); + + // Verify header + const sigA = r.readUint32LE(); + const sigB = r.readUint32LE(); + const ver = r.readUint8(); + + if (sigA !== SIGNATURE_A || sigB !== SIGNATURE_B) { + throw new Error(`Invalid portable storage signature: 0x${sigA.toString(16)} 0x${sigB.toString(16)}`); + } + if (ver !== FORMAT_VER) { + throw new Error(`Unsupported portable storage version: ${ver}`); + } + + return readSection(r); +} + +function readSection(r) { + const count = r.readVarint(); + const obj = {}; + for (let i = 0; i < count; i++) { + const keyLen = r.readUint8(); + const keyBytes = r.readBytes(keyLen); + const key = new TextDecoder().decode(keyBytes); + obj[key] = readEntry(r); + } + return obj; +} + +function readEntry(r) { + const typeTag = r.readUint8(); + const isArray = (typeTag & FLAG_ARRAY) !== 0; + const baseType = typeTag & ~FLAG_ARRAY; + + if (isArray) { + const count = r.readVarint(); + const arr = []; + for (let i = 0; i < count; i++) { + arr.push(readValue(r, baseType)); + } + return arr; + } + + return readValue(r, baseType); +} + +function readValue(r, type) { + switch (type) { + case TYPE_UINT64: return r.readUint64LE(); + case TYPE_UINT32: return r.readUint32LE(); + case TYPE_UINT16: return r.readUint16LE(); + case TYPE_UINT8: return r.readUint8(); + case TYPE_INT64: return r.readInt64LE(); + case TYPE_INT32: return r.readInt32LE(); + case TYPE_INT16: return r.readInt16LE(); + case TYPE_INT8: return r.readUint8(); // signed but same read + case TYPE_DOUBLE: return r.readDouble(); + case TYPE_BOOL: return r.readUint8() !== 0; + case TYPE_STRING: return r.readString(); + case TYPE_OBJECT: return readSection(r); + case TYPE_ARRAY: return readEntry(r); // nested array + default: + throw new Error(`Unknown portable storage type: ${type}`); + } +} diff --git a/src/wallet-sync.js b/src/wallet-sync.js index 6c533aa..2278c69 100644 --- a/src/wallet-sync.js +++ b/src/wallet-sync.js @@ -13,7 +13,7 @@ import { WalletOutput, WalletTransaction } from './wallet-store.js'; import { cnSubaddressSecretKey, carrotIndexExtensionGenerator, carrotSubaddressScalar } from './subaddress.js'; import { scanCarrotOutput, makeInputContext, makeInputContextCoinbase, generateCarrotKeyImage } from './carrot-scanning.js'; -import { parseTransaction, extractTxPubKey, extractPaymentId } from './transaction.js'; +import { parseTransaction, parseBlock, extractTxPubKey, extractPaymentId } from './transaction.js'; import { bytesToHex, hexToBytes } from './address.js'; import { TX_TYPE } from './wallet.js'; import { @@ -21,6 +21,7 @@ import { generateKeyImage, commit as pedersonCommit, deriveViewTag, computeSharedSecret, ecdhDecodeFull, deriveSubaddressPublicKey, scalarAdd, + cnFastHash, } from './crypto/index.js'; // ============================================================================ @@ -359,45 +360,12 @@ export class WalletSync { const headers = headersResponse.result.headers || []; - // Process blocks in batches using batch RPC for better performance - const BLOCK_BATCH_SIZE = 100; // Fetch 100 blocks per RPC call - - - for (let i = 0; i < headers.length; i += BLOCK_BATCH_SIZE) { + for (const header of headers) { if (this._stopRequested) break; - - const batch = headers.slice(i, i + BLOCK_BATCH_SIZE); - const heights = batch.map(h => h.height); - - // Batch fetch all blocks at once - const blocksResponse = await this.daemon.getBlocksByHeight(heights); - - - if (!blocksResponse.success || !blocksResponse.result?.blocks) { - // Fallback to individual fetches if batch fails - for (const header of batch) { - if (this._stopRequested) break; - await this._processBlock(header); - await this.storage.putBlockHash(header.height, header.hash); - this.currentHeight = header.height + 1; - this._emit('syncProgress', this.getProgress()); - } - continue; - } - - // Process each block from batch response - const blocks = blocksResponse.result.blocks; - for (let j = 0; j < blocks.length; j++) { - if (this._stopRequested) break; - - const header = batch[j]; - const blockData = blocks[j]; - - await this._processBlockFromBatch(header, blockData); - await this.storage.putBlockHash(header.height, header.hash); - this.currentHeight = header.height + 1; - this._emit('syncProgress', this.getProgress()); - } + await this._processBlock(header); + await this.storage.putBlockHash(header.height, header.hash); + this.currentHeight = header.height + 1; + this._emit('syncProgress', this.getProgress()); } // Save sync height @@ -439,84 +407,6 @@ export class WalletSync { }); } - /** - * Process a block from batch response - * @private - */ - async _processBlockFromBatch(header, blockData) { - // blockData from get_blocks_by_height.bin contains block blob - // We need to parse it or use the included transactions - - - // If blockData has the structure we need, use it directly - // Otherwise fall back to individual fetch - if (!blockData || !blockData.block) { - return this._processBlock(header); - } - - try { - // Parse block blob to get miner_tx and protocol_tx - // The block field contains the hex-encoded block - const blockBlob = blockData.block; - - // For now, decode the block JSON if available, otherwise parse binary - // Most daemon responses include txs array with the transactions - const txs = blockData.txs || []; - - // Process miner_tx from block header data if available - if (blockData.miner_tx_hash) { - // Find miner_tx in txs array or parse from block - const minerTx = txs.find(tx => tx.tx_hash === blockData.miner_tx_hash); - if (minerTx && minerTx.as_json) { - await this._processEmbeddedTransaction( - JSON.parse(minerTx.as_json), - blockData.miner_tx_hash, - header, - { isMinerTx: true, isProtocolTx: false } - ); - } - } - - // Process protocol_tx if available - if (blockData.protocol_tx_hash) { - const protocolTx = txs.find(tx => tx.tx_hash === blockData.protocol_tx_hash); - if (protocolTx && protocolTx.as_json) { - await this._processEmbeddedTransaction( - JSON.parse(protocolTx.as_json), - blockData.protocol_tx_hash, - header, - { isMinerTx: false, isProtocolTx: true } - ); - } - } - - // Process regular transactions - for (const tx of txs) { - if (tx.tx_hash === blockData.miner_tx_hash || tx.tx_hash === blockData.protocol_tx_hash) { - continue; // Already processed - } - if (tx.as_hex) { - await this._processTransaction(tx, header, { isMinerTx: false, isProtocolTx: false }); - } - } - - // Emit new block event - this._emit('newBlock', { - height: header.height, - hash: header.hash, - timestamp: header.timestamp, - txCount: txs.length, - hasMinerTx: !!blockData.miner_tx_hash, - hasProtocolTx: !!blockData.protocol_tx_hash - }); - - } catch (error) { - // Fallback to individual block processing - console.error(`Batch processing failed for block ${header.height}, falling back:`, error.message); - await this._processBlock(header); - } - } - /** * Process a single block * @private