Skip to content

Conversation

@tomusdrw
Copy link
Member

@tomusdrw tomusdrw commented Jan 17, 2026

Metric Native (μs) Wasm (μs) Diff (Wasm − Native) (μs) % change vs Native Factor (Wasm / Native)
min 2062.0 4008.8 1946.8 +94.4% 1.94x
max 17919.2 49823.2 31904.0 +178.1% 2.78x
mean 5500.6 10773.7 5273.1 +95.9% 1.96x
median 5380.6 9715.9 4335.3 +80.6% 1.81x
stdDev 2640.5 4921.5 2281.0 +86.4% 1.86x
p90 8217.8 17508.8 9291.0 +113.1% 2.13x
p99 14072.5 24409.0 10336.5 +73.5% 1.73x
== native
==                 min: 2062.0 [μs]
==                 max: 17919.2 [μs]
==                 mean: 5500.6 [μs]
==                 median: 5380.6 [μs]
==                 stdDev: 2640.5 [μs]
==                 p90: 8217.8 [μs]
==                 p99: 14072.5 [μs]
== wasm
==                 min: 4008.8 [μs]
==                 max: 49823.2 [μs]
==                 mean: 10773.7 [μs]
==                 median: 9715.9 [μs]
==                 stdDev: 4921.5 [μs]
==                 p90: 17508.8 [μs]
==                 p99: 24409.0 [μs]

@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

The PR restructures the Bandersnatch VRF project from a single crate into a three-member Rust workspace (core, wasm-binding, native-binding). It introduces multi-platform native builds via updated CI workflows, implements a new publishing pipeline for platform-specific packages, and adds TypeScript coordination layer that selects native or WASM bindings at runtime based on environment.

Changes

Cohort / File(s) Summary
CI/CD Workflows
.github/workflows/ci.yml, .github/workflows/publish.yml
Restructured CI to build native targets (macOS arm64, Linux x64). Modified publish workflow to multi-stage: build-wasm, build-native (with matrix), then publish. Added artifact upload/download, version bumping with git SHA, and platform-specific package publishing.
Rust Workspace Structure
bandersnatch/Cargo.toml, bandersnatch/core/*
Converted root Cargo.toml to workspace configuration with members [core, wasm-binding, native-binding]. Added core library with VRF verification APIs, RingSize enum, error handling, FFI module for interop.
Native Node.js Bindings
bandersnatch/native-binding/*, bandersnatch/npm/darwin-arm64/*, bandersnatch/npm/linux-x64-gnu/*
Created native-binding crate (napi cdylib) wrapping core FFI. Added platform-specific npm packages for darwin-arm64 and linux-x64-gnu with .node binary references. Configured strict OS/CPU/Libc constraints per platform.
WebAssembly Bindings
bandersnatch/wasm-binding/*
New WASM library crate with wasm-bindgen exports delegating to core FFI functions. Seven public functions matching native binding API surface.
TypeScript/JavaScript Coordination
bandersnatch/npm/native/index.js, bandersnatch/npm/native/index.mjs, bandersnatch/npm/native/index.d.ts, bandersnatch/npm/native/package.json
Dual ESM/CJS native binding loaders detecting platform and loading target-specific .node file. TypeScript types define API surface. Package exports root entry with dual module entry points.
TypeScript Dual-Binding Adapter
bandersnatch/src/index.ts, bandersnatch/src/env.d.ts
New init() function choosing native binding (Node) or WASM binding (browser) at runtime. Unified BandersnatchApi interface with camelCase methods. Lazy initialization, error tracking, and environment detection.
Root Package Configuration
bandersnatch/package.json, package.json, native/package.json
Updated root package.json to version 0.2.0 with explicit exports map (., ./wasm, ./native). Added native as optionalDependency. Bumped monorepo version, added @napi-rs/cli devDependency and native workspace. Updated native/package.json with exports, optional native bindings.
Build Scripts & Config
bandersnatch/build-native.sh, bandersnatch/tsconfig.json, native/tsconfig.json, native/index.ts
New bash script detecting target arch and copying compiled .node artifacts to npm directories. TypeScript configs for strict compilation. Updated native/index.ts import paths to wasm-binding subdirectories.
Removed / Deleted
bandersnatch/src/lib.rs, bandersnatch/src/main.rs
Deleted original single-crate Rust implementation and test main. Logic moved to core library.
Misc
bandersnatch/.gitignore
Added rule to ignore .node files under npm/ directory.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mateuszsikora
  • DrEverr
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly describes the main change: native bandersnatch bindings are being introduced as a new feature.
Description check ✅ Passed The PR description provides benchmark performance comparisons between native and Wasm implementations, demonstrating the practical impact of the native bindings being added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

npm workspaces don't respect os/cpu restrictions, causing npm ci to fail
on Linux when trying to install darwin-arm64 packages. The platform
packages are build artifacts, not development dependencies.
@tomusdrw tomusdrw marked this pull request as ready for review January 19, 2026 21:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces native Node.js bindings for the Bandersnatch VRF library with automatic platform detection and WASM fallback. The implementation demonstrates a ~2x performance improvement over WASM (as shown in the benchmark data), while maintaining backward compatibility through optional dependencies.

Changes:

  • Refactored Bandersnatch Rust code into a core library shared by both WASM and native bindings
  • Added N-API native bindings for Node.js (darwin-arm64 and linux-x64-gnu)
  • Implemented TypeScript wrapper with automatic native/WASM binding selection
  • Updated CI/CD workflows to build and publish both native and WASM artifacts
  • Version bump from 0.0.4 to 0.2.0 across all packages

Reviewed changes

Copilot reviewed 27 out of 38 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
package.json Version bump to 0.2.0, added @napi-rs/cli dependency and new workspace for native bindings
package-lock.json Updated lockfile with new dependencies and workspace entries for native packages
bandersnatch/core/ New shared Rust core library with FFI-ready functions
bandersnatch/native-binding/ New N-API native bindings using napi-rs
bandersnatch/wasm-binding/ Extracted WASM-specific bindings from main package
bandersnatch/src/index.ts New TypeScript wrapper with automatic native/WASM selection
bandersnatch/npm/ Platform-specific native packages (darwin-arm64, linux-x64-gnu, native wrapper)
native/index.ts Updated WASM import path to point to new wasm-binding location
.github/workflows/ Updated CI/CD to build native bindings on multiple platforms

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.github/workflows/publish.yml:
- Around line 36-66: The workflow references matrix.settings.package in the
Upload artifact step but the matrix only defines host and target; update the
build-native matrix entries to include a package key for each settings entry
(e.g., add package: <npm-package-dir-name> alongside host and target) so ${{
matrix.settings.package }} resolves correctly in the Upload artifact path, or if
you prefer not to extend the matrix, change the Upload artifact path to a
concrete path that does not rely on matrix.settings.package; edit the matrix
definition near build-native (matrix.settings) and the Upload artifact step
(uses: actions/upload-artifact@v4) accordingly.

In `@bandersnatch/npm/native/package.json`:
- Around line 5-18: The package.json declares "type": "module" but the CJS entry
points to index.js which is parsed as ESM; rename the CommonJS implementation
file index.js to index.cjs and update package.json: change "main" to
"./index.cjs" and update the exports map so the "require".default points to
"./index.cjs" (leave "module" -> "./index.mjs" and "types" unchanged) so ESM and
CJS consumers load the correct files.

In `@bandersnatch/src/index.ts`:
- Around line 31-37: The wasm init call in loadWasmBinding is incorrectly
passing an object wrapper; update the call to pass the WasmBinding.InitInput
directly to the generated init function. Locate loadWasmBinding and replace the
call to wasmBindingModule.default that currently wraps wasmModule in an object
(referenced as wasmBindingModule.default) so it instead calls
wasmBindingModule.default with wasmModule as the sole argument.
🧹 Nitpick comments (12)
native/tsconfig.json (1)

1-14: Consider a broader include glob to catch nested TS files.

Using "include": ["*.ts"] won’t typecheck files in subdirectories if the native package grows. A broader glob reduces maintenance overhead.

♻️ Suggested tweak
-  "include": ["*.ts"],
+  "include": ["**/*.ts"],
   "files": ["env.d.ts"]
bandersnatch/core/Cargo.toml (1)

14-15: Redundant dev-dependency.

hex is already declared as a regular dependency on line 11 with the same version. The dev-dependency entry is unnecessary since regular dependencies are also available in tests.

🔧 Suggested fix
-[dev-dependencies]
-hex = "0.4.3"
-
 [features]
native/index.ts (1)

29-33: Consider simplifying async wrappers.

The double async/await is slightly redundant since the inner functions already return promises. This is a minor style preference and works correctly as-is.

🔧 Optional simplification
 export const init = {
-  bandersnatch: initOnce(async () => await bandersnatchInit({ module_or_path: await bandersnatchWasm() })),
-  ed25519: initOnce(async () => await ed25519Init({ module_or_path: await ed25519Wasm() })),
-  reedSolomon: initOnce(async () => await reedSolomonInit({ module_or_path: await reedSolomonWasm() })),
+  bandersnatch: initOnce(async () => bandersnatchInit({ module_or_path: await bandersnatchWasm() })),
+  ed25519: initOnce(async () => ed25519Init({ module_or_path: await ed25519Wasm() })),
+  reedSolomon: initOnce(async () => reedSolomonInit({ module_or_path: await reedSolomonWasm() })),
 };
bandersnatch/build-native.sh (2)

35-39: Consider adding artifact existence check before copy.

If the cargo build succeeds but produces an unexpected output path or filename, the cp will fail with a less informative error. Adding a check improves debuggability.

🔧 Suggested improvement
 artifact="$target_dir/release/libbandersnatch_native.$lib_ext"
 dest_dir="$ROOT/npm/$package"
 
+if [ ! -f "$artifact" ]; then
+  echo "Error: Expected artifact not found at $artifact"
+  exit 1
+fi
+
 mkdir -p "$dest_dir"
 cp "$artifact" "$dest_dir/bandersnatch.$package.node"

20-33: Limited platform support.

The script only supports aarch64-apple-darwin and x86_64-unknown-linux-gnu. Consider documenting this limitation or adding support for additional common targets (e.g., x86_64-apple-darwin for Intel Macs, aarch64-unknown-linux-gnu for ARM Linux) as the project grows.

bandersnatch/npm/native/index.js (1)

1-1: Remove unused import.

createRequire is imported from the module package but never used in this file. The ESM version (index.mjs) does use it, but this CommonJS version uses require directly.

🔧 Suggested fix
-const { createRequire } = require('module');
-
 function loadNativeBinding() {
.github/workflows/publish.yml (1)

104-106: Minor: Consider quoting shell variable.

Static analysis flagged SC2086 on git rev-parse --short HEAD. While git short SHAs are alphanumeric and safe, quoting is a good defensive practice.

🔧 Suggested fix
       - id: vars
-        run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+        run: echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
bandersnatch/npm/native/index.mjs (1)

12-31: Confirm libc/OS coverage for Linux targets.

On Alpine/musl, linux + x64 will still try the glibc binary, which typically fails at load time. If musl is unsupported, detect it and throw an explicit “unsupported platform” error; if it is supported, add a musl mapping to avoid confusing loader errors.

bandersnatch/package.json (1)

8-27: Verify ESM-only export surface is intentional.

The exports map only defines import, and with "type": "module" any require('@typeberry/bandersnatch') will fail. If CJS consumers are still supported, add a require condition and a CJS build/wrapper; otherwise document the breaking change.

bandersnatch/src/index.ts (1)

84-97: Initialization is idempotent but InitOptions ignored on re-init.

When already initialized, the function returns early without applying options. This is correct behavior but could be documented for clarity. Consider adding a JSDoc note that options are only used on first init.

📝 Optional: Add JSDoc clarification
+/**
+ * Initialize the Bandersnatch binding.
+ * Options are only applied on the first call; subsequent calls return the existing API.
+ */
 export default async function init(options?: InitOptions): Promise<BandersnatchApi> {
bandersnatch/core/src/lib.rs (2)

34-42: RingSize::from_size silently defaults to Tiny for unrecognized sizes.

Any size that doesn't match Full.size() (1023) will default to Tiny. This could mask configuration errors. Consider returning a Result or Option, or at minimum document this behavior.

📝 Consider explicit error handling or documentation
     /// Create a RingSize from a numeric size.
+    /// Returns `Tiny` if size doesn't match a known ring size.
     pub fn from_size(size: usize) -> Self {
         if size == Self::Full.size() {
             RingSize::Full
+        } else if size == Self::Tiny.size() {
+            RingSize::Tiny
         } else {
-            RingSize::Tiny
+            // Log warning or consider returning Option<RingSize>
+            RingSize::Tiny // Default fallback
         }
     }

107-111: deserialize_public_key silently returns padding point on failure.

This silent fallback to a padding point could mask deserialization errors. While this may be intentional for ring operations, consider documenting this behavior explicitly.

📝 Add documentation for fallback behavior
-/// Deserialize a public key from bytes, returning the padding point on failure.
+/// Deserialize a public key from bytes.
+/// 
+/// Returns the ring padding point on deserialization failure, which allows
+/// invalid keys to be safely included in ring operations without causing panics.
 pub fn deserialize_public_key(chunk: &[u8]) -> Public {

@tomusdrw tomusdrw marked this pull request as draft January 20, 2026 10:07
@tomusdrw tomusdrw marked this pull request as ready for review January 20, 2026 10:31
@tomusdrw tomusdrw merged commit 913922c into main Jan 20, 2026
4 checks passed
@tomusdrw tomusdrw deleted the td-native branch January 20, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants