Files
zeekayandHanzo Dev c22f13f63e cert: the weight clause Go runs last, the set admitted whole, and one workspace
THE TOTAL. `Register` in `validator/registration.go` runs seven clauses, and
this port had six. The seventh is `math.Add64` on the running weight: a set
whose weights sum past what a `uint64` holds is refused entire, before any of
it is admitted. Without it `total_stake` folded with `saturating_add` and
`voted_stake` summed with `saturating_add`, so a set of four validators at
2^63 reported `u64::MAX` staked AND `u64::MAX` voted — and
`u64::MAX > floor(2·u64::MAX/3)`. A certificate carrying HALF the stake
cleared the two-thirds export floor, which is the rung a bridge reads.

Clamping is what makes the two sides agree on a number neither can represent,
so neither clamps any more. The set carries its total as a fact established at
admission with `checked_add` — one source of truth, decided at the one place
that still has a registration to refuse — and `voted_stake` is checked too:
the voters are a subset of the members, so against a set this crate admitted
the overflow is unreachable, and reaching it is evidence about the stake
source rather than about the votes. `weighted_quasar` takes the same
treatment: a vector with no representable total leaves by the same door as a
zero total, since a plain sum WRAPS to a small total and sizes the count gate
below the stake quorum it exists to anticipate.

THE SET. `insert` admits one validator and reports on that one, which is a
property no set can be built out of: a caller that inserts in a loop and logs
what failed is left holding a set missing a member, with `n` short by one and
the total short by that member's stake — the two numbers every threshold is
read against, wrong, with nothing to say so. `ValidatorSet::register` is the
port of Go's whole-set door: sorted by node id so which duplicate a set is
refused on cannot depend on the order a caller built its input, and
all-or-nothing because the set under construction is a local. `insert` stays
for a membership change to a set already live, and now says so.

THE BINDING. `pkg/typescript` path-depends on `pkg/rust` and could not
compile: narrowing the node id from 32 bytes to 20 left two `NodeID::from([u8;
32])` calls with no impl. It is a 20-byte API now, and the 20 bytes come from
20-byte input — `arr[..20]` would reintroduce exactly the collision the
narrowing exists to prevent, so a hex string of the wrong length is refused,
never trimmed. The width is `size_of::<cert::NodeId>()`, taken from the core
rather than restated, so it cannot drift again. `addValidator` also stopped
discarding the core's answer, which reported a validator as added when the
set had refused it.

And the two crates are one workspace, because that is what they already were.
Built separately they can disagree and did — nothing built both, so nothing
noticed. `cargo test` at the root now builds both, which turns this class of
break from a review finding into a compile error. It also surfaced two tests
in the binding that had been failing unnoticed: they voted with unregistered
ids, from before only members were counted.

No signed byte moves. `canonical_vote_message` is untouched, the node id
enters no signed message, and the Go corpus and the frozen `pop` vectors pass
unchanged.

Verified: 97 tests green (92 rust, 5 napi), clippy clean at the workspace
root, the binding's 29 JS tests green against a linux build of it. Red's PoC
— four validators of 2^63, two voting, read at QUASAR — returned `Ok(())`
before and now dies at registration with `WeightOverflow`.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:53:54 -07:00
..

@luxfi/consensus

TypeScript/Node.js bindings for the Lux Consensus SDK.

Native performance through NAPI-RS with full TypeScript type safety.

Features

  • Native Performance: Rust-based consensus engine compiled to native Node.js addon
  • Type Safety: Full TypeScript definitions for all APIs
  • 69% Byzantine Tolerance: Industry-leading fault tolerance (2% above standard 67%)
  • 2-round finality API (block time / finality latency not measured for this binding)
  • Quantum-Ready: Optional ML-DSA (FIPS 204) post-quantum signatures
  • GPU Acceleration: Optional MLX (Apple Silicon) and CUDA support

Installation

npm install @luxfi/consensus
# or
pnpm add @luxfi/consensus
# or
yarn add @luxfi/consensus

Quick Start

import {
  ConsensusEngine,
  testnetConfig,
  VoteType,
  generateBlockId,
} from '@luxfi/consensus';

// Create engine with testnet configuration
const engine = new ConsensusEngine(testnetConfig());
engine.start();

// Create and add a block
const blockId = generateBlockId();
engine.addBlock({
  id: blockId,
  parentId: '00'.repeat(32), // Genesis parent
  height: 1,
  payload: Buffer.from('Hello, Lux!').toString('hex'),
  timestamp: Date.now(),
});

// Register the validators, then vote. Only members are counted, and a node id
// is 20 bytes — 40 hex characters.
const config = testnetConfig();
for (let i = 0; i < 9; i++) {
  const voter = i.toString(16).padStart(40, '0');
  engine.addValidator(voter, 100);
  engine.recordVote({
    blockId: blockId,
    voteType: VoteType.Preference,
    voter: voter,
  });
}

// Check if accepted
console.log('Block accepted:', engine.isAccepted(blockId)); // true

engine.stop();

Configuration

Default Configuration

import { defaultConfig } from '@luxfi/consensus';

const config = defaultConfig();
// {
//   alpha: 20,           // 69% quorum threshold
//   k: 20,               // Sample size
//   maxOutstanding: 10,  // Max concurrent polls
//   maxPollDelayMs: 1000,
//   networkTimeoutMs: 5000,
//   maxMessageSize: 2097152, // 2MB
//   securityLevel: 5,    // NIST Level 5
//   quantumResistant: true,
//   gpuAcceleration: true,
// }

Testnet Configuration

Lower thresholds for development and testing:

import { testnetConfig } from '@luxfi/consensus';

const config = testnetConfig();
// alpha: 5, k: 5, quantumResistant: false, gpuAcceleration: false

Mainnet Configuration

Production settings with full security:

import { mainnetConfig } from '@luxfi/consensus';

const config = mainnetConfig();
// alpha: 20, k: 21, quantumResistant: true, gpuAcceleration: true

API Reference

ConsensusEngine

The main consensus engine class.

class ConsensusEngine {
  constructor(config?: ConsensusConfig);

  start(): void;
  stop(): void;

  addBlock(block: Block): void;
  recordVote(vote: Vote): void;
  recordVotesBatch(votes: Vote[]): number;

  isAccepted(blockId: string): boolean;
  getStatus(blockId: string): BlockStatus;
}

Types

interface Block {
  id: string; // 32-byte hex
  parentId: string; // 32-byte hex
  height: number;
  payload: string; // hex-encoded
  timestamp: number; // Unix ms
}

interface Vote {
  blockId: string;
  voteType: VoteType;
  voter: string; // 20-byte hex — a node id, not a block id
  signature?: string;
}

enum BlockStatus {
  Unknown,
  Processing,
  Rejected,
  Accepted,
}

enum VoteType {
  Preference,
  Commit,
  Cancel,
}

Platform Support

Platform Architecture Status
macOS arm64 ✅
macOS x64 ✅
Linux arm64 (glibc) ✅
Linux x64 (glibc) ✅
Linux arm64 (musl) ✅
Linux x64 (musl) ✅
Windows x64 ✅

Building from Source

# Install dependencies
pnpm install

# Build release
pnpm build

# Build debug
pnpm build:debug

# Run tests
pnpm test

Performance

Not independently benchmarked. This package is a NAPI wrapper around the Rust FFI binding, which itself wraps the C library — it measures data-structure insertion, not consensus. The honest consensus-bound ceiling is the C-FFI rate of ≈21K votes/sec; a wrapper cannot exceed it. Earlier per-second throughput figures here were retracted. See .github/workflows/README.md and the "Honest Assessment" block in LLM.md.

License

MIT License - See LICENSE for details.

Contributing

Contributions are welcome! Please see our Contributing Guide.