c2pool: a Monero pool with no operator and no daemon (mainnet milestone, 25 XMR)
What this is
I've been building a mining pool for Monero that has no operator and needs no daemon. Several independent nodes run it together. Each node is its own Monero node, keeps the same ledger of who is owed what, and pays miners straight from the coinbase of the blocks the pool finds. There is no wallet held by anyone, so there is nothing to seize and nothing to run off with.
I'm asking for one milestone: run it on mainnet with several nodes, find a block, and pay the miners it owes in that block. 25 XMR, about eight weeks from funding. The rest of this post explains what is different about it, what works today, and what does not work yet.
I don't run a public persona. The code and the chain are the record. Judge the work, not the name.
Why another pool
p2pool removed the operator, and that was the right idea. But it has costs. Each p2pool node needs a full monerod beside it, and the miner runs a third program to mine. A share below the sidechain difficulty earns nothing, so small miners were pushed onto mini and nano chains. A share that arrives late becomes an uncle and is paid less. And a sidechain can only grow so far before it has to split again, and each split pays only its own miners.
I wanted to see how many of those costs could be removed without bringing the operator back.
What is different
No daemon. The pool node is a Monero node. It follows the chain over Monero's own peer-to-peer network, checks proof of work, and keeps its own index. It verifies transactions, ring signatures and key images included, against an output set built from a snapshot of the chain at a fixed height. Before it serves, it checks that snapshot against several live peers. It rebuilds the transaction pool from the network's relay, so its blocks carry fee-paying transactions, and it sends a found block out over the same peer-to-peer links. Templates, the tip, the RandomX seed and finality all come from its own index. On mainnet the snapshot's output set is 18 GB against 113 GB for a full monerod database. On stagenet a node ran in about 1.3 GB of memory. It is not an archival node. It starts from the snapshot, not from genesis, and I would like each release to pin that snapshot by hash so anyone with their own monerod can reproduce it. That pinning is part of this milestone.
A built-in miner. The same program can mine on the machine's processor. A small miner installs one thing and it joins the pool and mines. It uses the same RandomX code the node verifies with. It's off by default, and anyone with real hardware points xmrig at the stratum port as usual. It has built templates, found blocks and settled them end to end on a private regtest.
No uncles. A share is a work receipt: evidence of work that stands on its own, not a position in a chain. Late, concurrent or reordered shares count in full. Work is credited to the time it was done, in time bins with a grace window, not to when it arrived. A well-connected miner gets nothing extra over a slow one. The right grace window is one of the things the next stagenet run measures, across continents.
One ledger, checked by every node. Every node keeps the same record of who is owed what, and every block the pool finds commits a digest of it. A node that disagrees shows up at once. The coinbase pays whoever has been owed longest, by one rule every node runs the same way. I call it Work Receipt Settlement: you are paid for the receipts of the work you did. A block can't pay everyone, so whoever isn't paid in this block keeps the balance for a later one. Nothing is lost, and the size of the coinbase stops limiting how many miners the pool can hold. Monero has no consensus cap on coinbase outputs, so here this is cheap. A log of owed events in a Merkle Mountain Range, so any entry can be proved, is built and switched off.
Raindrops into a bucket. A share below the pool target is not thrown away. The near-misses a miner submits in an interval are raindrops. From how deep the best of them reach, the node estimates the work behind them, without bias. Who is counted is fixed before the work is done, so splitting yourself into many names gains nothing. The credit goes into your owed balance, the bucket, until the payout reaches you. That lets one pool serve a phone and a farm with no mini or nano chain. Where this stands: it's built and switched off on Monero. When I switched it on in a test, every node collected the same raindrops and agreed on the ledger, but the credit came out wrong. In short intervals it lowered a miner's credit instead of adding to it. That has to be fixed before it goes live, along with two other problems listed below.
Roundabouts. When the pool grows past what one group of nodes should check, it splits into roundabouts, groups that pass shares among themselves. A roundabout splits in two above twice a reference hashrate, and two join again below it, with a margin so they don't flap. Each stays between about 0.7 and 2.5 times the reference. The map is computed from the finalized chain, so there is no vote, and miners are placed by a hash of their identity, so nobody picks their roundabout. The important part is that balances don't live in a roundabout. They live in the one ledger every node keeps. A block found by any roundabout pays whoever is owed longest across the whole pool. When roundabouts split or join, no balance moves, gets divided or gets lost, and a miner keeps its place in the queue. So a small or unlucky roundabout can't starve its miners, and the work a small miner needs to be seen stays the same as the pool grows. p2pool split by miner size and each piece paid only its own. This splits by load and stays one pool with one queue. It is designed and built as separate modules with tests, switched off. Wiring it into the node comes later.
The payout rule is checked by machine. It is written as a formal specification and checked mechanically, so the accounting can be audited without trusting me.
Put together: a pool like this at about a tenth of Monero's hashrate finds around 72 blocks a day and pays about as regularly as a centralized pool of the same size. With raindrops on, every miner down to one small device is owed its exact share and is paid when the queue reaches it.
What it does not do
It does not remove the pool's own luck. A lucky day pays more, an unlucky day less. Only someone holding a float can promise a fixed payment per share, and this pool holds nothing for anyone.
A single roundabout is cheaper to attack than the whole pool, because it holds only part of the hashrate. How far a majority inside one roundabout can go is still open, and I don't have a full answer yet.
Each node keeps a row per miner. That is fine for millions of miners, not for billions.
Fees
There is no pool fee. As in p2pool and in my earlier engine, each block carries one small output to the project's donation address, which every node checks, and a miner can choose to give more. A node runner can take a fee only by crediting part of its own node's work to itself, and the other nodes can check that. There is no operator key to steal.
One thing I found in review and am changing before the mainnet run. Today, while no miner is owed anything yet (a new pool's first block, and any block found before that one is confirmed), the whole block reward goes to the donation address, and the miners are paid from later blocks. That leaves the pool one block behind for good, with that amount sitting in the donation wallet. I don't want that. The fix is that a block pays the miners whose work is in it right away, and the donation output keeps only its marker. It is part of this milestone.
Where it stands
The code is public at https://github.com/frstrtr/c2pool under the AGPL-3.0. The design paper is at https://frstrtr.github.io/c2pool/.
Working now, on the main branch:
- The node follows stagenet over Monero's peer-to-peer network with no daemon call on the chain view.
- It rejects forged ring signatures and double spends. Its verdicts match monerod's on the same bytes, tested on regtest.
- Three nodes ran together on stagenet for three hours. They found nine blocks. Eight are in the main chain, and one lost a race and was never counted. All three finalized the same blocks and agreed on the ledger at every one. Every block carried the transactions waiting in the pool, and every coinbase paid exactly the reward plus fees.
- A mainnet snapshot has been made from my own synced monerod.
- A node restarts from its saved state in seconds instead of re-reading from the snapshot. On stagenet it re-read 22 blocks instead of 1300.
Next is a longer stagenet run: three nodes on three machines, one of them on another continent, for at least a day, with restarts and a network split along the way. I'll post the results in this thread.
Not done yet: the mainnet run, which is what I'm asking to fund; paying small work on Monero; a long parity soak with an outside review; and FCMP++.
The milestone
At least three nodes on separate machines run against Monero mainnet with no daemon. They follow the tip, verify blocks and transactions, build templates that carry fee-paying transactions, find a block, send it to the network, and pay the miners it owes in that block's coinbase. The nodes agree on the ledger.
What you get: a running pool any miner can connect to, a release that pins the snapshot by hash so anyone with a monerod can reproduce it, and a short report with the heights found, the coinbase payouts, and each node's ledger digest at those heights. Target is eight weeks from funding. The funds are earned on completion, and the report goes in this thread.
What comes after
Each of these would be its own proposal, judged on what gets delivered.
- Paying small work on Monero. Three problems first. A short interval with too few samples can lower a miner's credit. The record of which miners take part is kept by each node alone and has to be shared, or nodes can disagree. And raindrops relayed during a network split aren't re-sent afterwards yet.
- A long parity soak next to monerod on mainnet, and an outside review of the payout accounting, before anyone is asked to point real hashrate at it.
- Roundabouts wired into the node.
- FCMP++ and Carrot. The planned upgrade changes transaction and address formats, and the node's verification and coinbase code have to follow.
Why fund it
It lets a small machine take part in a pool without a daemon. It keeps the pool without an operator across several nodes. It pays late work in full. And it is built to pay small work and to grow without splitting the pool's income. It wants what p2pool wants; it is not trying to replace it.
Track record
Verify by the work. c2pool is a C++ reimplementation of p2pool.
- My earlier engine and my maintained p2pool forks run real pools. The public Dash pool at dash.voidbind.com has mined mainnet blocks you can check on any Dash explorer, for example 2520018 and 2519922. The Litecoin pool with Dogecoin merged mining runs on the public p2pool network (https://github.com/frstrtr/p2pool-merged-v36), where the v36 share format is now active.
- The new engine this proposal builds on has run on a public Dash testnet, where two independent nodes reached the same payout state and the testnet chain-locked the blocks they found.
- The Monero lane is newer than all of that, and I'm treating it that way.
Tooling
I use Anthropic's Claude models through their Claude for Open Source Program, which gives open-source maintainers six months of free access. That is tooling, not money, and it doesn't fund this milestone. I can share the acceptance notice with anyone who wants to check it.
Licence
AGPL-3.0. Anyone who distributes it, or runs a modified version as a network service, has to publish the source, so any miner can see what the pool they join actually runs. The block-template assembler is ported from p2pool under the GPL-3.0 and combined under the AGPL-3.0's section 13. The Monero cryptography it uses is BSD-3-Clause.
If you find a hole in any of this, tell me. I'd rather hear it now than after someone points hashrate at it.