Skip to content

feat(stratum_v2): add SV2 support#65

Draft
jayrmotta wants to merge 9 commits into
256foundation:mainfrom
jayrmotta:feature/sv2-support
Draft

feat(stratum_v2): add SV2 support#65
jayrmotta wants to merge 9 commits into
256foundation:mainfrom
jayrmotta:feature/sv2-support

Conversation

@jayrmotta
Copy link
Copy Markdown

@jayrmotta jayrmotta commented Jun 5, 2026

Summary

This pull request introduces Stratum V2 support to Mujina, it's the result of #54 with guidance from Ryan and Plebhash.

The SV2 team has great crates like channels_sv2, sv2-apps, and so on, which made possible for Mujina to reuse the same APIs, behaviors, and patterns used to build their proxies, pools, and so on which will be Mujina's counterparties. In other words, we abstract away a big part of the stratum-related code like the Noise handshake, frame processing, state machine, and so on.

We ended up with a lot less domain-specific protocol code, and more dealing with the intricacies of a firmware like being shutdown-safe, having safe defaults and checks, interfacing and providing feedback to the user.

This pull request is in draft and open for comments, suggestions, etc. I will continue to edit it as I prepare it for a final review.

Steps to try it out

Assuming you have this repo cloned and checked out this pull request's branch. Just export the variables as we normally do for SV1 servers, but using the stratum2+tcp:// scheme and suffixing it with the authority key 9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72 as described in their spec.

$ export MUJINA_POOL_URL="stratum2+tcp://75.119.150.111:3333/9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
$ export MUJINA_POOL_USER="<bitcoin payout address>.<worker name>"

Then you can try with regular log level or run it with debug, that would help if you see any problems as you test.

$ RUST_LOG=mujina_miner=debug cargo run

jayrmotta added 9 commits June 4, 2026 15:06
  Add the stratum_v2 module with a channel-based StratumV2Client that
  handles DNS resolution, TCP connection, Noise_NX handshake, and the
  SetupConnection / OpenExtendedMiningChannel sequence. The event loop
  drives incoming SV2 frames, outgoing commands, and shutdown via a
  select! loop.

  Add StratumV2Error with an is_fatal() predicate to distinguish
  recoverable from unrecoverable protocol failures.

  Delegates all network and cryptographic plumbing to stratum-apps
  (v0.4, features = ["network", "core", "config"]) rather than
  hand-rolling Noise framing and async DNS.
Move ExponentialBackoff and ConnectOutcome out of stratum_v1.rs into a
new job_source/connection.rs module so both the V1 and V2 sources can
share the same implementation without copy-paste drift.
  Parse pool URLs into a typed PoolEndpoint, detecting the protocol
  from the scheme and extracting the Base58Check-encoded authority
  public key required for the Noise_NX handshake in Stratum V2.

  New types: PoolProtocol (StratumV1 | StratumV2), PoolEndpoint with
  host, port, protocol and authority key, and ParseEndpointError with
  typed variants for each failure mode.

  PoolConfig gains an endpoint() convenience method. The authority
  public key field is private so the invariant — StratumV2 always
  carries a key, StratumV1 never does — cannot be violated by direct
  struct construction; callers use authority_pubkey() instead.

  Unrecognised URL schemes (stratum://, tcp://, etc.) now return
  UnsupportedScheme rather than silently producing a garbage host
  that fails only at DNS resolution time.
  Add SourceEvent::SharesAccepted(u32) so SV2 sources can report
  pool-acknowledged shares back to the scheduler. SV1 has no
  per-batch ACK, so this variant will only be emitted by SV2
  sources.

  ForcedRateSource passes the event through unchanged; it only
  modifies job templates.

  The scheduler increments a new shares_accepted counter on receipt
  and includes it in periodic status log output alongside
  shares_submitted.
Implement StratumV2Source, bridging StratumV2Client events into the
scheduler's job-source abstraction. Converts NewExtendedMiningJob/
SetNewPrevHash pairs into JobTemplates using MerkleRootKind::Computed,
manages the connection lifecycle with exponential back-off, and routes
pool outcomes (shutdown, pool-requested reconnect, connection loss)
through ClientOutcome. Future jobs are buffered as Option<_> and
activated on a matching SetNewPrevHash; a SetNewPrevHash with no
matching job emits ClearJobs. Share submission is stubbed pending
further wiring.
Build and forward SubmitSharesExtended on every incoming share.
Track submitted sequence numbers in a VecDeque; drain on
SubmitSharesSuccess using RFC 1982 serial arithmetic and remove
individually on SubmitSharesError.

Validate channel_id on pool ACKs before mutating pending state.
Emit SharesAccepted and SharesRejected so the scheduler can
track pool-acknowledged and pool-rejected share counts.
Parse MUJINA_POOL_URL with PoolEndpoint::parse() to detect the pool
protocol. stratum2+tcp:// URLs construct and spawn StratumV2Source;
all other schemes fall through to the existing Stratum V1 path.
  If task_to_job_full fails after current_task.replace(), the ntime ticker
  holds a broken task that can never produce valid work.  Move the conversion
  ahead of the replace so a failure leaves current_task unchanged and the
  ticker is unaffected.
  Spin up a real SV2 pool via integration_tests_sv2 behind a Sniffer proxy
  and assert on the full message exchange at each key protocol step:
  SetupConnection fields, Extended Channel open, job delivery, share
  acceptance and rejection, stale-share filtering, ClearJobs on a missing
  future job, and extranonce2 zero-padding to the pool's allocation.

  The clean_shutdown_before_connect test requires no external binaries and
  runs in the normal suite.  The remaining tests download Bitcoin Core and
  the sv2-tp binary on first run (~200 MB, cached) and are marked #[ignore].
@jayrmotta
Copy link
Copy Markdown
Author

jayrmotta commented Jun 5, 2026

My recent tests had 100% shares accepted, and that's expected as we use the same validate_share function used by the pool (tested against the SRI pool). Some shares do however fail the validation and report errors, as far as I could observe all related to duplicate shares.

I did some investigations and I suspect we suffer from the same issues that led to bitaxeorg/ESP-Miner#420 which was also triggered by esp-miner's implementation of SV2 support. Something for a separate discussion and implementation, but I believe has to happen given that allowing for higher hashrate is something that matters more for Mujina then it does for ESP-Miner.

I was wondering if a share that fails validate_share should be accounted as rejected, even if the rejection came from the client itself and not the upstream server. Right now it's not being accounted, hence the 100% success.

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.

1 participant