* Select Apple Metal 4.0.1 kernel; keep 4.0.0 u64 elsewhere
NVIDIA was ~37% slower on the 4.0.1 Metal-tuned kernel. Split
Poseidon2 kernels and pick by backend:
- Metal + SHADER_INT64: v4.0.1 u64
- other SHADER_INT64: v4.0.0 u64
- no SHADER_INT64: 32-bit fallback (same as 4.0.0)
CI uploads the Linux release binary as an artifact so we can
bench NVIDIA without building on the GPU box.
* Drop version numbers from GPU kernel labels
Log native-u64 vs native-u64 Apple Metal without tying
the strings to release tags.
CI resolved the floating stable channel, so Rust 1.98 shipped the
chunks_exact_to_as_chunks clippy lint and failed checks that passed on
an older local stable. Pin the exact version, matching how chain pins
its toolchains.
Apple M5 Pro, CLI benchmark: 24.0 -> 43.2 MH/s. Kernel output is
unchanged and bit-exact against the CPU reference.
Field arithmetic:
- Deferred-carry additions (Acc): the external layer and the internal
row sum accumulate unreduced with a carry counter and fold once per
output instead of two epsilon corrections per add.
- gf64_mul_add folds the row sum into the 128-bit product before a
single plain reduction.
- mul_wide / gf64_sqr assemble the 128-bit product from 32-bit-valued
partial sums with no carry compares.
Kernel structure (code size dominates on Apple's compiler; every
unrolled variant measured slower):
- One loop drives all 30 rounds so each layer is emitted once, with
round constants added by the linear layer preceding each S-box
(RC_EXT table, RC_INTERNAL padded with a trailing zero).
- One S-box site with a runtime lane count (12 external, 1 internal).
- mining_main runs pad / first squeeze / second squeeze through a
single inlined permute64 call in a phase loop.
Measured and rejected: uniform buffers and unhoisted loads, workgroup
sizes 64/128/512, 8-add MDS, one-compare reduce, fully unrolled
layers, two nonces per thread.
* docs: update systemd example for the serve CLI with required node auth
The unit and readme still described the old HTTP-API miner (MINER_PORT,
MINER_ENGINE, no subcommand). Update ExecStart to 'serve' and document the
now-required MINER_AUTH_TOKEN_FILE / MINER_TLS_CERT_SHA256_FILE, including
how to copy the node's miner-auth-token and miner-tls-cert-sha256 files
past ProtectHome=true, and refresh the env var reference to the current
CLI.
* docs: fix env-file example and CPU-worker claims per review
- Move inline comments off the env assignments: systemd EnvironmentFile=
keeps inline '# ...' text as part of the value, so the copied example fed
Clap unparseable values and the unit crash-looped under Restart=always.
- Describe the real worker behavior: CPUs are counted via the process
affinity mask (num_cpus); unset MINER_CPU_WORKERS auto-detects ~50%, and
an explicit value is used as-is — there is no clamp or warning.
- Drop the 'logs the detected cpuset mask' claim from the unit and override
comments; no such logging exists in the miner.
* docs: metrics exporter is always on; purge-chain does not rotate credentials
- The Prometheus exporter starts unconditionally and binds plaintext HTTP on
0.0.0.0:9900 by default; MINER_METRICS_PORT only changes the port. Say so
everywhere instead of 'when metrics are enabled', and add a hardening note
to firewall the port.
- Rephrase the credential-rotation pitfall: purge-chain removes only the
database, so the node reloads the same miner-auth-token and TLS cert;
rotation only happens when the base path changes or the files themselves
are deleted.
* Send miner auth token in Ready handshake.
Require --auth-token or --auth-token-file when connecting to a node so the
miner matches the node's shared-secret QUIC auth.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Pin the node's miner TLS certificate by SHA-256 fingerprint.
Require --tls-cert-sha256 or --tls-cert-sha256-file so miners reject
MITM certs instead of accepting any self-signed server certificate.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fmt
* Docs: prefer reading miner auth token from file, not node logs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Version the miner ALPN as quantus-miner/2 to match the node.
A node/miner protocol mismatch now fails cleanly at the TLS handshake with
"no application protocol" instead of an opaque auth error.
Co-authored-by: Cursor <cursoragent@cursor.com>
* point at git
* Depend on published quantus-miner-api 0.3.0 from crates.io.
The temporary git branch pin is no longer needed now that 0.3.0 is published.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fail closed on permanent auth/TLS misconfig instead of reconnect looping.
Validate the fingerprint and Ready frame size at miner/pool startup before
metrics, workers, or HTTP come up. Treat node auth rejection and pin mismatch
as PermanentConnectError (no retry), and only reset reconnect backoff after
the first NewJob proves auth succeeded.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Point miner auth docs and examples at miner-auth-token / TLS pin files.
The node never logs the auth token; CLI/pool help and every serve/Docker
example now require the chain config files (or env vars) instead of implying
credentials can be copied from logs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fmt
* Remove the miner Dockerfile and Docker packaging docs/CI.
Container GPU access is a poor fit for this miner, and the Docker examples
were unusable with SocketAddr (hostnames rejected). Run the native binary
instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Shut down the pool on permanent upstream auth failure; keep reconnecting otherwise.
Supervise run_node_client alongside HTTP so a bad token/pin ends the process
instead of leaving a live API on a dead upstream. Node restarts still reconnect
with backoff; the current job is cleared while disconnected so captchas are not
issued against a stale header.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace obsolete EXTERNAL_MINER_PROTOCOL.md with a pointer to MINING.md.
The local copy still described the pre-auth protocol (no Ready token, no pin,
old ALPN, 16 MB frames). Canonical docs live in quantus-miner-api and the
node's MINING.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Delete EXTERNAL_MINER_PROTOCOL.md; README already points at MINING.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Investigating reported seal submission failures. Root cause analysis:
the miner's job-id staleness chain (worker check, quic-loop filter,
node-side job_id compare) cannot mislabel a candidate — internal and
node job ids are updated atomically together. The remaining benign race
is node-side: a block-template rebuild (new pre_hash, same parent) does
not bump the node's job counter, so an in-flight seal mined on the old
template passes the job-id check and fails seal verification, logging
"Failed to submit seal". Fixing that belongs in the chain repo (bump the
job counter on template rebuild).
Miner-side hardening in this commit:
- gpu_cpu_parity is now a two-phase fuzzer. Phase 1 dumps FULL 512-bit
hashes for randomized (header, nonce) pairs via the real
midstate-resume datapath and verifies each against the canonical CPU
implementation, multi-threaded (defaults to 1M hashes, ~5s; 10M runs
in ~15s). Phase 2 fuzzes whole mining jobs with five profiles: random,
carry-edge starts (saturated low limbs), 2^256-boundary crossings,
impossible difficulty with exact hash-count assertion, and CPU-known
solutions to catch false negatives. Seeded and reproducible; also
asserts the submitted work bytes match the nonce.
Verified clean: 10M bulk hashes + ~1,500 seals across 6 seeds.
- miner-service re-verifies every candidate on the CPU (one hash) before
sending JobResult; an engine-produced invalid seal is never submitted
and is logged loudly instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The benchmark fed each worker a fixed range (10k CPU / 1M GPU nonces),
capping GPU dispatches at 1M regardless of --gpu-batch-size, and
re-searched the same range on every iteration. Worker ranges now step
through the nonce space and are sized from the batch flags, floored at
the old widths so tiny flags keep engine-internal cancellation batching
instead of turning per-call harness overhead into the measured quantity.
Batch-size flags are validated non-zero at parse time (serve and
benchmark) and echoed in the benchmark banner.
Exploring a fully native Metal port of the mining kernel showed it runs
~16% faster than the wgpu path at equal batch size. Decomposing that gap:
~9% is naga's injected runtime bounds checks and forced loop bounding,
~1.5% is mulhi()-based multiplication (inexpressible in WGSL), <1% is
the constant address space; the rest is binding/codegen residue.
The dominant term is recoverable inside wgpu on every backend:
the engine now builds its pipelines with create_shader_module_trusted +
ShaderRuntimeChecks::unchecked(). This is sound for our shaders: sources
are compiled into the binary, every buffer access is a constant-bounded
loop index into fixed-size bindings the engine allocates itself, all
loops have static bounds, and both variants remain covered by the
dual-shader component suite and the CPU parity example.
Apple M4: 11.39 -> 12.34 MH/s at the default 1M batch (+8.3%), 11.74 ->
12.73 MH/s at 8M (+8.4%). Cumulative vs main: 4.4x.
New examples/trusted_hashrate.rs measures checked vs trusted on the same
shader and CPU-verifies results.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Evaluated all ideas from #80 against the native-u64 kernel. Its three
main wins (lazy field arithmetic, precomputed sponge state, conditional
second squeeze) were already present in stronger form; two micro-opts
carried over and measured positive at large batches (+1.3% at 16M,
noise-level at the 1M default):
- Hoist midstate/target/start-nonce storage reads out of the nonce loop.
- Produce byte-swapped hash words on demand during the target compare,
so the reject path skips building the swapped hash entirely.
Forcing nonces_per_thread=2 was also tested and regressed; one thread
per nonce stays.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three stacked optimizations, each verified bit-exact against the CPU
reference (qp-poseidon-core) by the dual-shader component suite and a
new GPU/CPU parity example:
- mining_u64.wgsl: native 64-bit Goldilocks arithmetic with plonky2-style
lazy reduction, used automatically when the adapter supports
SHADER_INT64 (all Apple Silicon, NVIDIA, modern AMD). mining.wgsl
remains the 32-bit fallback. (+50%)
- Midstate precompute: the first two of five sponge permutations absorb
the header and high nonce half, both constant per batch; they are now
computed once on the CPU (pow_core::mining_midstate) and resumed on
the GPU. Batches are clamped so nonces never carry past 2^256. (+77%)
- Lazy second squeeze: the first squeeze yields the most significant 256
bits of the hash, which decide hash-vs-target unless exactly equal to
the target's high half, so the common reject path skips the final
permutation. (+56%)
Apple M4 throughput: 2.81 -> 11.4 MH/s at the default 1M batch
(criterion large_range_1m/gpu: 355.8ms -> 86.3ms, -75.7%).
Also:
- Fix criterion GPU bench crash: thread-local worker resources are now
tagged with an engine id so multiple GpuEngines per process never mix
devices.
- Fix stale end-to-end test harness (6-binding layout) and run the full
component suite against both shader variants.
- New examples: hashrate, gpu_cpu_parity (25 jobs incl. 2^256-boundary
crossing), gpu_features.
- Add Apple M5 family GPU tiers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add captcha share pool service and browser WASM solver
pool-service connects to a node over the external-miner QUIC protocol and
turns browser captcha solves into real mining shares: sessions with disjoint
nonce ranges over the current header, single-use share tokens, a
reCAPTCHA-shaped /siteverify endpoint, and upstream block submission when a
share meets full network difficulty. solver-wasm is a raw C-ABI wasm32 build
of the Poseidon2 nonce grinder (no wasm-bindgen) for the embeddable widget.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fmt
* Re-queue block solution when upstream write fails
A network-difficulty share taken off the solution channel was permanently
lost if the JobResult write to the node failed: the connection loop broke to
reconnect but the block was no longer anywhere. Park the block in a pending
slot that survives reconnection and is retried before any other work.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fmt
* Address PR review: capacity limits, loud block drops, shared QUIC transport, constant-time secret compare
- Cap live sessions and tokens (configurable via --max-sessions/--max-tokens,
default 100k each); /api/session returns 503 at_capacity so a request flood
can no longer grow the maps without bound.
- A block solution that fails to queue upstream is now logged at error level,
and blocks_found/block_found only report solutions actually queued.
- Extract the node QUIC connect + insecure-verifier pattern into a shared
quic-transport crate used by both miner-service and pool-service; derive
the network target via pow_core::JobContext instead of re-deriving it.
- Compare the site secret in constant time (constant_time_eq).
- Note single-tenant token semantics on site_secret for the host-registration
follow-up.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add per-IP rate limiting on /api/session issuance
The global session cap stops unbounded map growth, but a single client could
still churn entries at the TTL boundary. Limit each IP to 60 issuances per
minute (configurable via --sessions-per-ip-per-min); excess requests get 429
rate_limited. Stale IP windows are GC'd alongside sessions/tokens.
Co-authored-by: Cursor <cursoragent@cursor.com>
* clippy
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* handle lost device correctly
* Update lib.rs
* allow-integrated flag otherwise exclude
* DeviceLost is a status
* Integrated GPU skipped when discrete init fails
* nits
On Windows wgpu enumerates each physical GPU once per backend (Vulkan +
DX12) plus a CPU-emulated fallback ("Microsoft Basic Render Driver").
Building a mining context for every entry causes VRAM contention and
OOMs the process during benchmark/serve startup (#61).
Instead of deduplicating by (vendor, device) PCI IDs - which would
collapse rigs with multiple identical cards into a single context - drop
CPU-emulated adapters and keep all adapters from the highest-ranked
backend present (Vulkan/Metal, then DX12). Within a single backend each
physical GPU appears exactly once, so identical cards are preserved by
construction and no physical-ID matching is needed.
Selected adapters are ordered discrete-first so `--gpu-devices 1` picks
the discrete card on hybrid laptops. Skipped adapters are logged at info
level; if nothing usable remains, init fails with an explicit error.
Selection logic is a pure index-based function unit-tested against the
exact enumeration reported in #61, identical multi-GPU rigs, DX12-only
machines, and software-only environments.
Co-authored-by: illuzen <illuzen@users.noreply.github.com>
* support AMD GPUs on windows
* add support for more GPUs
* remove shader dump from logs
* handle gpu initialization failure better
* fmt
* re-order matching to avoid mobile / desktop false positives
* harden gpu matching
* add tests to engine-gpu module
* handle dead device and failed mapping correctly
* handle timeout properly with tokio
* nits
* fix benchmarks
* fmt
* improve logging
* don't let the queue spill, just drain it
* move cancellation out of the shader, since it doesn't work
* simplify batch size mechanism, improve logs
* fmt
* address review items
* single hash, new poseidon
* fix build
* new poseidon api
* fmt
* fix race condition with stale jobs being reported
* fmt
* fix deps
* format toml
remove stale comments
---------
Co-authored-by: Nikolaus Heger <nheger@gmail.com>
* implement quic
* simpler names
* simplify worker logic
* simplify gpu configuration logic
* miner initiates and picks random start nonce
* remove mining service
* clean up logs and remove unused engines
* clean up metrics
* metrics fix
* shared worker pool, cpu driven cancellation
just 10x the gpu thru-put, no more batch-size on gpu
* fmt
* non-local git
* taplo derp
* fix the benches
* handle stale results correctly in gpu
* more accurate gpu hashrate
* fmt
* remove unused
* handle duplicate solutions, cli controls gpu cancel frequency
* put plonky2 back
* demote noisy log