Skip to content

Add Stratum V2 (SV2) protocol support#1553

Draft
warioishere wants to merge 20 commits intobitaxeorg:masterfrom
warioishere:feature/stratum-v2-support
Draft

Add Stratum V2 (SV2) protocol support#1553
warioishere wants to merge 20 commits intobitaxeorg:masterfrom
warioishere:feature/stratum-v2-support

Conversation

@warioishere
Copy link
Copy Markdown
Contributor

@warioishere warioishere commented Feb 14, 2026

Summary

Adds Stratum V2 binary protocol support alongside the existing V1 JSON-RPC implementation. Tested on a BM1370 bitaxe against a local SRI server and the SRI reference pool — full 1.3 TH/s hashrate with shares accepted.

What's included:

  • SV2 binary protocol: frame encoding/decoding, mining channel messages (SetupConnection, OpenStandardMiningChannel, NewMiningJob, SetNewPrevHash, SetTarget, SubmitSharesStandard)
  • SV2 Noise encryption: Noise_NX handshake with ChaCha20-Poly1305 transport via libsecp256k1 (git submodule v0.6.0)
  • Protocol coordinator with separate V1/V2 task files and fallback/recovery logic
  • Protocol selectable via NVS config and AxeOS Pool Settings UI (both primary and fallback pool)

What works:

  • Full hashrate (1.3 TH/s on BM1370)
  • Encrypted connection via Noise protocol
  • Share submission and acceptance
  • Pool difficulty tracking via SetTarget
  • V1/V2 protocol switching and fallback

Open decisions

none

Test plan

  • SV2 connection + Noise handshake against SRI server
  • Full hashrate (1.3 TH/s) with BM1370
  • Shares accepted, no duplicates
  • Dashboard loads in SV2 mode
  • V1 mode unaffected
  • Tested against SRI reference pool
  • Test on other ASIC variants (BM1366, BM1368, BM1397)

Test servers

Our own server (confirmed working):

  • Host: blitzpool.yourdevice.ch
  • Port: 3333
  • Authority Public Key: 9bCoFxTszKCuffyywH5uS5o6WcU4vsjTH2axxc7wE86y2HhvULU
  • running on mainnet

SRI reference pool (confirmed working):

  • Host: 75.119.150.111
  • Port: 3333
  • Authority Public Key: 9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72

For transparency: most of this implementation was done with the help of Claude (Opus 4.6). I hope that doesn't detract from the goal of bringing SV2 support to bitaxe.

@warioishere warioishere force-pushed the feature/stratum-v2-support branch from 8882a06 to 9551b7e Compare February 15, 2026 07:58
@mutatrum
Copy link
Copy Markdown
Collaborator

Please add some screenshots.

I've had a cursory look at the code, will do a more in-depth review later. I'm not opposed to agentic development, however, only if the author is familiar with the codebase and does first line code reviews as well. This codebase looks decent, but I did see some areas where it could benefit more from separation of concerns.

For example: would it be cleaner to have a dedicated stratum_v2_task.c, instead of intermingling the two protocols in stratum_task.c? This holds for all touch-points of sv2, try to make it as clean as possible, with the least amount of if sv else sv2 statements sprinkled throughout the code.

If you have Claude work on it, please make it aware of #901, and have it keep that in mind that if we're splitting off work production protocols, this is something that I might like to add as well in the future.

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Feb 15, 2026

Please add some screenshots.

I've had a cursory look at the code, will do a more in-depth review later. I'm not opposed to agentic development, however, only if the author is familiar with the codebase and does first line code reviews as well. This codebase looks decent, but I did see some areas where it could benefit more from separation of concerns.

For example: would it be cleaner to have a dedicated stratum_v2_task.c, instead of intermingling the two protocols in stratum_task.c? This holds for all touch-points of sv2, try to make it as clean as possible, with the least amount of if sv else sv2 statements sprinkled throughout the code.

If you have Claude work on it, please make it aware of #901, and have it keep that in mind that if we're splitting off work production protocols, this is something that I might like to add as well in the future.

Hey, I'll refactor this using a protocol coordinator pattern. I'll extract V1 into its own task (stratum_v1_task), remove the cross-protocol dependencies from sv2_task,
and add some helper functions for the shared operations like share submission. The coordinator will handle all the fallback/recovery logic instead of having tasks create each other.

I'm keeping create_jobs_task unified since it already works fine, and I don't think the global_state union or vtable stuff is worth the complexity. I'll remove the old stratum_task.c once everything tests out.

I read #901 already, but I thought about usings sv2 capability to connect to a separate jd-client that can connect to bitcoin core via Sjors TP, that would give us also full control over templates and dezentralize things further, but yea, I can have a look onto this option directly getting templates from core as its far simplier then using a jd-client.

We can look at this in further developement, okay?

@mutatrum
Copy link
Copy Markdown
Collaborator

mutatrum commented Feb 15, 2026

GetBlockTemplate mining is definitely for another PR, this is already quite big.

Some more code review remarks:

  • sv2_api.h filename mismatches sv2_protocol.c;
  • How much effort is it to support NewExtendedMiningJob so coinbase_decoder can be hooked up?
  • Not sure if the way the config works makes sense now:
    image The connection security radiobox is mutually exclusive with the protocol options, as is the sv2 Pool Fingerprint. It would make more sense to first have the Protocol option, and make the sv1/sv2 options only visible for the selected protocol. E.g. only show Connection Security on SV1, only show Pool Fingerprint on SV2;
  • Primary and secondary pool should both have the sv2 option. Not sure how the liveliness check on the primary pool should work with sv2?
  • Make naming a bit more consistent: sv2_task is unclear, which task is it? The current naming is not perfect either, but at least it shouldn't be made more confusing :-)

On libsecp256k1:

  • With the submodule, does the project checkout change now? And how does this transition for people already having checked out the project?
  • How is the version of the dependency managed?
  • Both questions might need stuff in the readme added.

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Feb 16, 2026

New clones need git clone --recursive, and existing checkouts need a one-time git submodule update --init --recursive after pulling, otherwise the build fails on an empty directory.

The submodule is pinned to commit 0cdc758 (tagged v0.6.0 of bitcoin-core/secp256k1). Git tracks the exact commit hash, so everyone gets the same version. Updating means checking out a new tag in the submodule dir and committing the pointer change. I will add this to the README.

I'd love to see extended channel support as well, but I think it makes sense to handle it in a separate PR. For now, let's prioritize standard channels here.

Standard channels have a clear advantage for a device like the BitAxe: the network overhead is minimal since the pool precomputes everything and the miner only receives the block header. Extended channels would require handling the full coinbase transaction on the miner side, which adds complexity here now to review and test.

Once standard channels are solid, we can build on that foundation and add extended channel support with coinbase TX handling in a follow-up PR.

Will keep the sv2_api.h filename mismatches sv2_protocol.c in mind during refactoring now and also consider a better naming.

Currently, we only have options based on what Protocol we choose so users dont get confused:

grafik

and for Sv2

grafik

I have also added Sv2 Mode for Fallback Pool.

grafik

Would you like to have everything in a single commit after refactoring? Should be done by this evening.

@warioishere warioishere force-pushed the feature/stratum-v2-support branch from 9551b7e to 0470129 Compare February 16, 2026 16:35
@warioishere
Copy link
Copy Markdown
Contributor Author

Refactoring is done, everything squashed into a single commit.

Here's what changed since last time: V1 and V2 now live in their own task files (stratum_v1_task.c and stratum_v2_task.c), with a protocol coordinator handling all the fallback and recovery logic. The old stratum_task.c is gone. Fixed the sv2_api.h naming mismatch, it's consistently sv2_protocol now.

Both primary and fallback pool support SV2 selection, and the UI only shows relevant options per protocol. Heartbeat probing works for both V1 and V2 primaries, so liveness checks are covered regardless of protocol combo.

Also fixed a bunch of stability issues with protocol switching - the old implementation would crash or restart the device when switching between V1 and V2 at runtime. That's sorted now.

Extended channel support and GetBlockTemplate mining are left for follow-up PRs as discussed.

Ready for review 🚀

@mutatrum
Copy link
Copy Markdown
Collaborator

No need to do squash and force-push. This makes it harder to review incremental changes.

@warioishere
Copy link
Copy Markdown
Contributor Author

i made a backup of the branch before squashing, should I revert?

@mutatrum
Copy link
Copy Markdown
Collaborator

i made a backup of the branch before squashing, should I revert?

No it's fine for this PR. Sometime a squash and/or force push is necessary to fix merges, so no problem. Just for future reference, the code will be squashed into a single commit on merge anyways.

@mutatrum
Copy link
Copy Markdown
Collaborator

Can you see if you can update your branch, it looks like some changes that were added to master have been added here as well. These might drop out if you update.

Comment thread main/tasks/protocol_coordinator.h
@warioishere warioishere force-pushed the feature/stratum-v2-support branch from 0470129 to b3d3a46 Compare February 16, 2026 18:37
@warioishere
Copy link
Copy Markdown
Contributor Author

Can you see if you can update your branch, it looks like some changes that were added to master have been added here as well. These might drop out if you update.

Updated the branch, rebased onto latest master. The 3 duplicate commits dropped out. Should be a cleaner diff now.

Add full Stratum V2 mining protocol support to the bitaxe, enabling
encrypted communication with SV2 pools via Noise_NX handshake
(secp256k1+EllSwift, ChaChaPoly, SHA256). Includes a robust
protocol coordinator for clean failover between any combination
of V1/V2 primary and fallback pools without device restarts.

Protocol implementation:
- SV2 binary protocol (components/stratum_v2/): SetupConnection,
  OpenStandardMiningChannel, NewMiningJob, SetNewPrevHash, SetTarget,
  SubmitShares with proper frame encoding/decoding
- Noise encryption (sv2_noise.c): Full NX handshake with optional
  authority public key verification (TOFU mode when unconfigured)
- libsecp256k1 v0.6.0 as git submodule for elliptic curve operations

Protocol coordinator and fallback:
- Non-blocking event-driven coordinator manages protocol task lifecycle
- Supports all 4 failover combinations: V2->V1, V2->V2, V1->V2, V1->V1
- Timer-based heartbeat probes primary pool during fallback operation
- User-selected fallback (dashboard toggle) disables auto-recovery
- Clean state transitions: queue clear, share stats reset, proper
  task shutdown with event synchronization

Key reliability fixes:
- Heap-allocate sv2_conn to prevent dangling pointer after task exit
- Dynamic protocol check in create_jobs_task (was cached at startup,
  causing memory corruption on protocol switch)
- Single event per task exit (was double-signaling coordinator)
- Remove esp_restart() from V1 task, notify coordinator instead
- Fix V1 transport handle leak (destroy after close)
- Remove close_connection race from asic_result_task

Frontend and configuration:
- NVS settings for SV2 authority pubkey and fallback pool protocol
- Pool settings UI: protocol selector and SV2 pubkey for both pools,
  V1-only options hidden when SV2 selected
- Display: hide block height and scriptsig in SV2 mode (not available
  in standard channel), show protocol indicator instead
- OpenAPI spec updated with new SV2 configuration fields
@warioishere warioishere force-pushed the feature/stratum-v2-support branch from b3d3a46 to 51787d9 Compare February 16, 2026 18:40
coordinator_state_t and coordinator_event_t are only used inside
protocol_coordinator.c, no need to expose them in the header.
@jbesraa
Copy link
Copy Markdown

jbesraa commented Feb 17, 2026

Which parts of SV2 protocol does this implement, is it Mining protocol or JD as well? Does it include both extended and standard channels support?
Would be nice if you could provide instructions on how to test this with my bitaxe.

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Feb 17, 2026

currently no extented channels, that will be part of a next PR. and no JD-client, but I am building an easy to setup full stack deployment which you could run on a raspberry pi, or even on the node itself. Contains a JD-Client, TP from Sjor, and a bitcoincore setup build with --enable-multiprocess so that the IPC Unix socket connection method can be used:

https://github.com/warioishere/sv2-apps

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Feb 17, 2026

jst confirmed the implementation works also againstthe original sv2 reference server:

authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" 
pool_address = "75.119.150.111" 
pool_port = 3333 

@mutatrum
Copy link
Copy Markdown
Collaborator

Do you have a Max (BM1397) device to test against? The others are functionally equivalent with respect to job construction/asic comms.

@plebhash
Copy link
Copy Markdown

ntime rolling for ASIC work generation

ntime rolling on a bitaxe seems quite backwards?

@plebhash
Copy link
Copy Markdown

plebhash commented Feb 17, 2026

elaborating on the comment above

IIUC the BM1370 doesn't support version rolling, which is forcing you to roll ntime so you can stick with a standard channel, right?

This is explicitly allowed by the SV2 spec.

"allowed" but not definitely not encouraged... rolling ntime can have bad consequences on consensus level

as a rule of thumb, ntime should only be increased when we want to reset the search space and we know we've been hashing for longer than 1s

and it should happen like this:

  • increment 1 unit on ntime, hash for at least 1s
  • increment 1 unit on ntime, hash for at least 1s
  • ...

or at least some variation that respects ntime as something that progresses together with real time, and not something that rolls indefinitely into the past or future (with undesired consequences)


overall, I think it's a very good idea to support Sv2 Standard Channels as a "first class citizen" on AxeOS

but for edge cases where ASICs cannot do version rolling, I would go with Extended Channels and not try to reinvent the wheel

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Feb 17, 2026

IIUC the BM1370 doesn't support version rolling

Can you elaborate on this? From what I see in the codebase, the BM1370 does do hardware version rolling:

  • BM1370_set_version_mask() writes the mask to register 0xA4 on the chip, and it's called during init with 0x1fffe000
  • The ASIC result struct includes a 16-bit version field that gets shifted left 13 (version_bits = ntohs(asic_result.job.version) << 13), matching the mask exactly
  • The rolled version is reconstructed via OR: version | version_bits

These are latest-gen Antminer S21 chips — would be surprising if they dropped version rolling support. The BM1397 is the only one where set_version_mask is a no-op placeholder.

That said, you're right that the ntime rolling approach is wrong regardless. Even if the ASIC does version roll, bumping ntime by +1 every 500ms job send is not how ntime should be used. I'll fix that — either remove the offset entirely (since version rolling gives enough search space) or clamp it to real elapsed wall-clock time.

What would you suggest as the right approach here for standard channels?

@warioishere
Copy link
Copy Markdown
Contributor Author

Do you have a Max (BM1397) device to test against? The others are functionally equivalent with respect to job construction/asic comms.

the BM1397 is the only ASIC where set_version_mask is a no-op — it doesn't do hardware version rolling. That means SV2 standard channels won't give it enough search space even with jst ntime rolling, which isn't a good solution (see discussion with plebhash above).

I think there are two options here:

  1. Exclude the BM1397 from SV2 support in this PR entirely, and only enable it later when we add extended channel support
  2. Drop SV2 support for the Bitaxe Max altogether, given it's the oldest generation

What do you think makes more sense?

@mutatrum
Copy link
Copy Markdown
Collaborator

Excluding a chip from a protocol is a bit of a nasty dependency. Maybe adding Extended channels also puts it more in line with how SV1 currently works. I can't really oversee how much more work that is to support though.

@warioishere
Copy link
Copy Markdown
Contributor Author

Yeah, I agree excluding a chip entirely from a protocol isn't great. I think the clean approach is:

  • BM1397 can't do hardware version rolling, so standard channels don't give it enough search space
  • BM1366/BM1368/BM1370 all have working version rolling, so standard channels work fine

The channel type decision is just a runtime check on the ASIC ID — use OpenExtendedMiningChannel for BM1397, OpenStandardMiningChannel for the rest. That way the BM1397 gets an extranonce to vary (like SV1 today), and the others get the simpler standard channel path.

For this PR I'd scope it to standard channels only, which means BM1397 stays on SV1 for now. Extended channel support in a follow-up PR would unlock SV2 for the Max as well.

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Feb 17, 2026

I looked into the effort for Extended Channel support. The good news is most of the heavy lifting already exists — calculate_coinbase_tx_hash() and calculate_merkle_root_hash() from the SV1 path do exactly what Extended Channels need (coinbase prefix + extranonce + suffix → merkle root). It's roughly ~700-800 lines of new code across 6 files, mainly new message parsers/builders in sv2_protocol.c and a generate_work_sv2_extended() that reuses the SV1 merkle computation.

One option would be to drop Standard Channels entirely and go straight to Extended Channels. That way:

  • All ASICs are supported (including BM1397/Max)
  • No ntime rolling needed — extranonce variation gives unlimited search space
  • Closer to how SV1 works today, so less conceptual difference
  • No need for channel-type branching logic

The tradeoff is that Extended Channels put more work on the miner (coinbase + merkle computation), but the ESP32 already does this for SV1 without issues.

What would you suggest — Extended-only, or keep both with a runtime ASIC check? Thats about a week more effort or so.

@plebhash
Copy link
Copy Markdown

IIUC the BM1370 doesn't support version rolling

Can you elaborate on this? From what I see in the codebase, the BM1370 does do hardware version rolling:

I'm not claiming this is true and I don't know whether BM1370 can do version rolling or not. I just got this understanding from the PR description.

The solution: Instead of rolling the version (which breaks BM1370's OR-based version reconstruction when carry transitions flip bits), we roll ntime on each work send. ntime is in the 2nd SHA-256 block (bytes 68-71), not in the midstate (bytes 0-63), so different ntime values produce unique hashes while midstates and the base version stay constant. This is explicitly allowed by the SV2 spec.

@sinnx3
Copy link
Copy Markdown

sinnx3 commented Mar 4, 2026

Built https://github.com/bitaxeorg/ESP-Miner/tree/early-access which includes this PR, upon setting up braiins via SV2 my 601 gamma fails over to secondary pool with this error
bitaxe-logs-2026-03-04T21-03-11-090Z.txt

Full braiins SV2 stratum url for reference:
stratum2+tcp://stratum.braiins.com:3333/9awtMD5KQgvRUh2yFbjVeT7b6hjipWcAsQHd6wEhgtDT9soosna

@warioishere
Copy link
Copy Markdown
Contributor Author

Built https://github.com/bitaxeorg/ESP-Miner/tree/early-access which includes this PR, upon setting up braiins via SV2 my 601 gamma fails over to secondary pool with this error bitaxe-logs-2026-03-04T21-03-11-090Z.txt

Full braiins SV2 stratum url for reference: stratum2+tcp://stratum.braiins.com:3333/9awtMD5KQgvRUh2yFbjVeT7b6hjipWcAsQHd6wEhgtDT9soosna

can you try standard channels, looks like braiins doesnt support extented channels. Also they dont completly follow SRI reference.

@sinnx3
Copy link
Copy Markdown

sinnx3 commented Mar 4, 2026

Built https://github.com/bitaxeorg/ESP-Miner/tree/early-access which includes this PR, upon setting up braiins via SV2 my 601 gamma fails over to secondary pool with this error bitaxe-logs-2026-03-04T21-03-11-090Z.txt
Full braiins SV2 stratum url for reference: stratum2+tcp://stratum.braiins.com:3333/9awtMD5KQgvRUh2yFbjVeT7b6hjipWcAsQHd6wEhgtDT9soosna

can you try standard channels, looks like braiins doesnt support extented channels. Also they dont completly follow SRI reference.

Working as intended with standard channels.
bitaxe-logs-2026-03-04T21-51-26-640Z.txt

@mutatrum
Copy link
Copy Markdown
Collaborator

mutatrum commented Mar 4, 2026

Built https://github.com/bitaxeorg/ESP-Miner/tree/early-access which includes this PR, upon setting up braiins via SV2 my 601 gamma fails over to secondary pool with this error bitaxe-logs-2026-03-04T21-03-11-090Z.txt
Full braiins SV2 stratum url for reference: stratum2+tcp://stratum.braiins.com:3333/9awtMD5KQgvRUh2yFbjVeT7b6hjipWcAsQHd6wEhgtDT9soosna

can you try standard channels, looks like braiins doesnt support extented channels. Also they dont completly follow SRI reference.

Are you sure? If Extended is not supported, why does it report Failed to parse OpenExtendedChannelSuccess, as seen in the logs?

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Mar 4, 2026

Built https://github.com/bitaxeorg/ESP-Miner/tree/early-access which includes this PR, upon setting up braiins via SV2 my 601 gamma fails over to secondary pool with this error bitaxe-logs-2026-03-04T21-03-11-090Z.txt
Full braiins SV2 stratum url for reference: stratum2+tcp://stratum.braiins.com:3333/9awtMD5KQgvRUh2yFbjVeT7b6hjipWcAsQHd6wEhgtDT9soosna

can you try standard channels, looks like braiins doesnt support extented channels. Also they dont completly follow SRI reference.

Are you sure? If Extended is not supported, why does it report Failed to parse OpenExtendedChannelSuccess, as seen in the logs?

I'm not 100% sure on all the details, but what I can confirm is that BraiinsOS itself does not follow the SRI reference implementation fully. I tested with an S9 running BraiinsOS and couldn't get it to connect to the SRI reference server or my own TypeScript implementation. After hours of testing I found the following differences from the miner side:

  1. Different handshake protocol: BraiinsOS sends a proprietary STR2 cipher negotiation preamble before the Noise handshake, which standard SV2 implementations don't expect.
  2. Different DH algorithm: BraiinsOS uses X25519 (32-byte keys) instead of EllSwift/secp256k1 (64-byte keys) that the spec defines.
  3. Different certificate format: BraiinsOS uses Ed25519 signatures (76 bytes including metadata) rather than Schnorr signatures.
  4. Different Noise prologue: BraiinsOS uses a special prologue format, while standard SV2 uses an empty prologue.
  5. Standard channels only: BraiinsOS only opens standard mining channels (OpenStandardMiningChannel)

But interestingly their server themself seems to also accept SRI reference handshake. But maybe on Extented Channels they use different message types and formats?

The Problem is, their server is closed source, I have no insights about it, I wrote them a long mail 3 weeks ago asking for some details, but they of course didn't bother to answer me. Maybe @GitGab19 has some insights.

Edit:

I'll add some logging tomorrow to my dev firmware and see if I can find out what formats they send.

@GitGab19
Copy link
Copy Markdown

GitGab19 commented Mar 5, 2026

Braiins is using an older version of the OpenExtendedMiningChannel.Success message, which has been modified a couple of months ago in the specs.

That's the reason why the parsing is failing when using extended channels.

We're in touch with them, and they are gonna update their implementation soon, in the meantime you can still test this PR using standard channels if you want to connect to Braiins.

@plebhash
Copy link
Copy Markdown

plebhash commented Mar 5, 2026

this was the breaking change: stratum-mining/stratum#2044

@GitGab19
Copy link
Copy Markdown

GitGab19 commented Mar 5, 2026

I and @jayrmotta are testing this PR on a Bitaxe Supra (BM1368) against the SRI pool (75.119.150.111:3333) with both extended and standard channels.

Extended channels work fine, but when using standard channels, we noticed that almost every share is submitted multiple times, and so we get many duplicate-share errors.

Here a screenshot of the AxeOS logs:
bitaxe-logs

Here a screenshot of the corresponding SRI pool logs:
sri-pool

@warioishere
Copy link
Copy Markdown
Contributor Author

@GitGab19

as discussed, I have removed ntime_rolling, and in the current state, the nonce space is not fully used. For standard channels to work, you need Adams PR420 on top of this as stated here c21f890

you can also use the early access branch which has everything included:

https://github.com/bitaxeorg/ESP-Miner/tree/early-access

@mutatrum
Copy link
Copy Markdown
Collaborator

mutatrum commented Mar 13, 2026

@GitGab19

as discussed, I have removed ntime_rolling, and in the current state, the nonce space is not fully used. For standard channels to work, you need Adams PR420 on top of this as stated here c21f890

you can also use the early access branch which has everything included:

https://github.com/bitaxeorg/ESP-Miner/tree/early-access

I'm testing with the early-access release and both test servers from the opening post result in a 100% error rate and do not generate a proper hashrate. It's possible I messed up something with the merge?

@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Mar 13, 2026

@GitGab19
as discussed, I have removed ntime_rolling, and in the current state, the nonce space is not fully used. For standard channels to work, you need Adams PR420 on top of this as stated here c21f890
you can also use the early access branch which has everything included:
https://github.com/bitaxeorg/ESP-Miner/tree/early-access

I'm testing with the early-access release and both test servers from the opening post result in a 100% error rate and do not generate a proper hashrate. It's possible I messed up something with the merge?

For testing I merged PR420 into this PR and everything works as it should. Didnt try the early access tree

warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 3, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 3, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 3, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
Resolve conflicts with upstream's BIP-110 signaling, field renames
(decode_coinbase_tx), self-test refactor, and ASIC common rename.
warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 4, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 4, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 5, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
warioishere added a commit to warioishere/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 5, 2026
Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults
@blackmennewstyle
Copy link
Copy Markdown

blackmennewstyle commented Apr 11, 2026

I and @jayrmotta are testing this PR on a Bitaxe Supra (BM1368) against the SRI pool (75.119.150.111:3333) with both extended and standard channels.

Extended channels work fine, but when using standard channels, we noticed that almost every share is submitted multiple times, and so we get many duplicate-share errors.

Here a screenshot of the AxeOS logs: bitaxe-logs

Here a screenshot of the corresponding SRI pool logs: sri-pool

I made a patch which totally solves the duplicated shares issue.

It is because the current code resets the extranonce2, every single job it receives. My patch forces it only when mining.notify has the cleanJob set to true. The duplicated shares are all gone with that patch.

#1603

It has been on hold since a while, hopefully it is taken in consideration at some point.

@warioishere
Copy link
Copy Markdown
Contributor Author

Hi, the dups on standard channels happens because currently the Asic cannot search the full nonce space. Pls read a view posts back.

A fix is already in progress here and fully solves the dups on standard channels

#420

@blackmennewstyle
Copy link
Copy Markdown

blackmennewstyle commented Apr 12, 2026

Hi, the dups on standard channels happens because currently the Asic cannot search the full nonce space. Pls read a view posts back.

A fix is already in progress here and fully solves the dups on standard channels

#420

Hi, the dups on standard channels happens because currently the Asic cannot search the full nonce space. Pls read a view posts back.

A fix is already in progress here and fully solves the dups on standard channels

#420

I'm afraid this won't fix the issue with the current condition in the code, resetting the extranonce2 for every single mining.notify is a mistake.

Also, are you actually testing on different stratum software? The current discussions do not clarify that point.

shufps added a commit to shufps/ESP-Miner-NerdQAxePlus that referenced this pull request Apr 12, 2026
* add Stratum V2 (SV2) protocol support with Noise encryption

Port of bitaxeorg/ESP-Miner#1553 (feature/stratum-v2-support) adapted
to the NerdQAxePlus C++ OOP architecture.

## Architecture
- Clean separation: no SV2 code in V1 files, no if/else protocol branching
- StratumTask split into StratumTaskBase (abstract) + StratumTaskV1 + StratumTaskV2
  using polymorphism for protocol-specific behavior
- MiningInfo split into MiningInfoBase (abstract) + MiningInfoV1 + MiningInfoV2Standard
  + MiningInfoV2Extended so create_jobs_task is fully protocol-agnostic
- StratumManager::createTask() factory selects V1 or V2 per pool config
- submitShare() extended with version_rolled + version_base to handle
  V1 (sends delta) and V2 (sends full version) correctly

## Components
- libsecp256k1 submodule (ElligatorSwift + Schnorr for Noise_NX handshake)
- stratum_v2 component: sv2_protocol.c (binary SV2 messages) and
  sv2_noise.c (ChaCha20-Poly1305 encryption) from ESP-Miner with
  extern "C" guards for C++ interop
- NoiseStratumTransport: wraps TCP + Noise handshake transparently
- StratumTaskV2: full SV2 lifecycle (SetupConnection, OpenChannel,
  job receive loop, share submission)
- MiningInfoV2Extended: computes coinbase + merkle root from SV2
  extended channel data with correct byte order for ASIC hardware

## Frontend
- Stratum Protocol dropdown (V1/V2) on Settings page
- SV2 Channel Type checkboxes (Extended/Standard) per pool tab
- SV2 Authority Public Key input field per pool tab
- Protocol label (SV1/SV2) shown on Dashboard pool info
- V1-only options (TLS, Extranonce Subscribe) hidden when SV2 selected
- Standard Channel marked as experimental (requires ASIC nonce space
  changes for full support)
- i18n: English + German

## Backend API
- GET/PATCH /api/system/info: stratumProtocol, sv2AuthorityPubkey,
  sv2ChannelType + fallback variants
- NVS config keys for all SV2 settings
- mbedtls ChaCha20-Poly1305 enabled in sdkconfig.defaults

* fix: trigger job creation on Extended Channel difficulty update

SetTarget was not triggering immediate job creation for Extended
Channel, causing shares with stale difficulty to be submitted
during pool difficulty adjustments. This matches how ESP-Miner
handles it via the global pool_difficulty + flag mechanism.

* fix: don't resend Standard Channel jobs on SetTarget

SetTarget updates difficulty for the next pool job. The ASIC keeps
mining with version rolling. Resending the same job caused
stale/difficulty-too-low rejects.

Extended Channel keeps trigger on SetTarget since each buildBmJob
produces unique work via extranonce_2.

* hide Standard Channel option in Dual Pool mode

Standard Channel is not compatible with Dual Pool because it relies
on long-running jobs (no 500ms resend). Channel type checkboxes are
now hidden when Dual Pool is selected, for both primary and fallback.

* disable Standard Channel, Extended Channel only for now

Standard Channel UI hidden (ngIf=false) and backend forced to
Extended Channel. Code preserved for future re-enabling.

To re-enable Standard Channel:
- UI: edit.component.html - change *ngIf="false" (2 places)
- Backend: stratum_task_v2.cpp - uncomment config read in protocolLoop()

---------

Co-authored-by: Thomas Shufps <shufps80@gmail.com>
@warioishere
Copy link
Copy Markdown
Contributor Author

warioishere commented Apr 12, 2026

Thanks for testing and reporting! We looked into this — the extranonce2 reset on clean_jobs=false is a V1-only issue since SV2 standard channels don't use extranonce at all (pool provides the full merkle_root), and SV2 extended channels manage their own extranonce counter independently.

We haven't been able to reproduce duplicate shares on SV2, neither on standard channels (with #420) nor on extended channels. Also on V1 this code has been running for years without duplicate share reports, so it might be specific to certain pool implementations that send more frequent mining.notify with clean_jobs=false.

We tested on our own SV2 server blitzpool and on SRI reference server.

Your fix in #1603 makes sense in theory though — @mutatrum what do you think, should that be reviewed as a separate fix on master?

Parse new_submits_accepted_count from the message and increment
the accepted share counter by that value instead of hardcoded 1.
Pools that batch-acknowledge (e.g. SRI in groups of 10) now count
correctly. Single-ack pools are unaffected.
@mutatrum
Copy link
Copy Markdown
Collaborator

mutatrum commented May 4, 2026

Can you update this PR, to not let it drift too far away.

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.

9 participants