Skip to content

non-custodial payouts extension - #203

Open
plebhash wants to merge 1 commit into
stratum-mining:mainfrom
plebhash:2026-07-27-non-custodial-extension
Open

non-custodial payouts extension#203
plebhash wants to merge 1 commit into
stratum-mining:mainfrom
plebhash:2026-07-27-non-custodial-extension

Conversation

@plebhash

@plebhash plebhash commented Jul 27, 2026

Copy link
Copy Markdown
Member

This PR proposes a protocol extension that enables non-custodial payouts with Stratum V2 Job Declaration Protocol (JDP), overcoming the fundamental limitations of base JDP (as discussed in #192).

This is an alternative to PRs #202 and #195

It tackles the problem from a different angle, which completely avoids RTT latency on job declaration.

More specifically, we leverage a push-based approach, and deliberately avoid constraining the design-space to a request-response message flow.

With regards to concerns @TheBlueMatt raised here:

You never know what kind of dust pruning policy the pool might want and trying to capture arbitrary payout construction rules sounds like it should get crazy complicated.

The formula/algo for dealing with dust and integer rounding proposed here (section 4) isn't exactly KISS, but it is sound, and leads to deterministic behavior on both JDS and JDC sides.

@plebhash
plebhash force-pushed the 2026-07-27-non-custodial-extension branch 30 times, most recently from fa7d92b to ab3fc3b Compare July 27, 2026 23:39
@warioishere

This comment was marked as resolved.

@plebhash

This comment was marked as resolved.

@plebhash

This comment was marked as resolved.

Comment thread extensions/0x0003-non-custodial-payouts.md Outdated
Comment thread extensions/0x0003-non-custodial-payouts.md Outdated
@warioishere

This comment was marked as resolved.

@warioishere

warioishere commented Jul 29, 2026

Copy link
Copy Markdown

as soon as the spec has a finally state, I am very happy to rewrite blitzpools JDS against this spec so we can test this together. Jst ping me here and I gonna give it ago.

@plebhash
plebhash force-pushed the 2026-07-27-non-custodial-extension branch 2 times, most recently from 9678d9f to 6b0d311 Compare July 30, 2026 18:02
Comment thread extensions/0x0003-non-custodial-payouts.md
Comment thread extensions/0x0003-non-custodial-payouts.md
Comment thread extensions/0x0003-non-custodial-payouts.md
Comment thread extensions/0x0003-non-custodial-payouts.md Outdated
Comment thread extensions/0x0003-non-custodial-payouts.md Outdated
Comment thread extensions/0x0003-non-custodial-payouts.md Outdated
@plebhash
plebhash force-pushed the 2026-07-27-non-custodial-extension branch from ec3e79b to 8272902 Compare July 31, 2026 17:13
Comment thread extensions/0x0003-non-custodial-payouts.md Outdated
@plebhash
plebhash force-pushed the 2026-07-27-non-custodial-extension branch from 8272902 to e4fde17 Compare July 31, 2026 23:34
@warioishere

This comment was marked as resolved.

@warioishere

This comment was marked as resolved.

@plebhash
plebhash force-pushed the 2026-07-27-non-custodial-extension branch from e4fde17 to 8c92309 Compare August 5, 2026 18:25
@plebhash

This comment was marked as resolved.

@warioishere

This comment was marked as resolved.

@plebhash

This comment was marked as resolved.

@warioishere

This comment was marked as resolved.

@warioishere

This comment was marked as resolved.

introduces SetPayoutDistribution message and distribution_id TLV for DeclareMiningJob/SetCustomMiningJob, enabling multi-miner coinbase payouts for non-debt accounting methods (PPLNS, SLICE, TIDES)
@plebhash
plebhash force-pushed the 2026-07-27-non-custodial-extension branch from 8c92309 to 721ccfd Compare August 6, 2026 15:54
@plebhash

This comment was marked as resolved.

@warioishere

This comment was marked as resolved.

@plebhash

Copy link
Copy Markdown
Member Author

awesome @warioishere please let us know when your implementation is finished and ready for testing

an ACK / PR approval would also be appreciated, but no rush (actually feel free to keep reviewing until you feel you have a solid grasp)

@warioishere

warioishere commented Aug 13, 2026

Copy link
Copy Markdown

ack from me on the approach.

blitzpool runs 0x0003 on all three of our payout modes now: pplns, team mining (I call it group-solo) and plain solo in base jdp mode without extention. solo doesn't really make much sense for jdp, and you should better mine on your own node, but it falls out of the same code path so it is covered and I plan do also do some kind of pool contests or gamification in future for solo miners, but not sure yet.

the one thing it cost me is that i had to move the whole pplns pool over to a weight based model to make it fit. sounds bigger than it was and it is definitely worth it, it saves me a lot of pain in a few situations.

what i did not do is test against an adapted jd-client, and that is on purpose. if i vibe code and build a testclient i am only testing my own reading of the spec against itself. if i misread something, both sides would be wrong in the same way and everything would look green.

@sweethashio

Copy link
Copy Markdown

We hit this same shape building payout distribution into our own pool (NexusPool), and there's one case I don't think the spec currently covers.

§9.3 is clear that value absorbed into pay_P is pool accounting policy and out of scope, and the example it gives is miners below a payout threshold. That's a deliberate decision by the pool, so leaving it out of scope makes sense to me.

The case I mean is a different one. payouts is SEQ0_64K[B0_64K], but nothing relates that to how many outputs the coinbase can actually carry, and nothing says what an implementation does when the selected set doesn't fit. The obvious behavior is to render what fits and let the rest fall through. That's silent, and the arithmetic hides it completely. Since weight[i] are relative to W, rendering the first k of n still gives a distribution where pay_P = T − Σ pay[i] holds exactly. The sum closes. No stated invariant breaks.

We wrote exactly that, and it took an adversarial review to catch it before it shipped. With a 200-entry window and 2 renderable slots, the two paid miners got about 1% of the block and 99% landed on the pool's own output, and every test we had still passed. It isn't the same thing as a threshold decision. Those are miners the pool picked to pay and then didn't, and neither side can tell that from the message.

What we ended up doing: refuse to build the job at all if the selected set doesn't fit, and push selection upstream, where it ranks by accrued unpaid credit instead of current-window share. A miner left out of one block keeps its credit and ranks up for a later one. Ranking by share is what makes the small miners never rotate in.

For context on where we ran into this: we've been building a signed parametric payout rule of our own (Pactum, part of a small suite we call Tessera), which is how the same truncation arithmetic ended up in front of us.

Might be worth a normative line here: either the sender MUST NOT propose a set the coinbase can't express, or the fit is the receiver's to check and reject. As written both sides can be fully conformant and a miner still silently gets nothing.

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.

4 participants