Guide
Run a mining pool
Set up tenrai-pool, the Tenrai reference pool, step by step with a node and a wallet daemon. Every option, fees, payouts and the API.
On this page
tenrai-pool is the Tenrai reference pool. It runs PPLNS and SOLO ports over stratum, credits rewards from the node's finalized cuts, pays miners through the wallet daemon and serves a website with a page per payout address.
What runs where
All three programs run on one machine.
| Program | Role |
|---|---|
tenraid | Synced node. The pool uses its mining listener 127.0.0.1:28702 and API 127.0.0.1:28701 |
tenrai-wallet daemon | The pool wallet. Its first address receives every block reward and pays the miners |
tenrai-pool | Stratum ports, accounting, payouts, website |
tenrai-pool is built from its own repository with cargo build --release. It needs Rust and a C compiler, like the node.
On mainnet the three programs find each other by their defaults: started in the order below, none of them needs an option. The options are for what differs from that, such as ports that miners reach from outside.
1. Run a synced node
Set up a node as in Run a node and wait until GET /v1/node/sync answers "synced": true.
2. Create the pool wallet
tenrai-wallet init --generate-passwordWrite down the recovery_phrase it prints. This wallet holds every block reward until it is paid out.
The wallet file lies in the wallet folder of this machine, ~/.tenrai/wallet/mainnet/wallet.vault on Linux and macOS and %LOCALAPPDATA%\Tenrai\wallet\mainnet\wallet.vault on Windows. --generate-password writes a random password to wallet.vault.pass beside it, readable by this user only, so the daemon opens the wallet unattended.
3. Start the wallet daemon
tenrai-wallet daemonIt listens on 127.0.0.1:28703. On its first start the daemon writes wallet.vault.token beside the wallet file, the key the pool uses to talk to it. Keep the daemon on 127.0.0.1.
4. Start the pool
tenrai-poolThe pool reads the daemon's token from the wallet folder and listens on 127.0.0.1: PPLNS on port 28740 and SOLO on 28744. To let miners in from outside and name the pool:
tenrai-pool --pplns-listen 0.0.0.0 --solo-listen 0.0.0.0 \
--pool-name "Example Pool" --public-host pool.example.orgA pool wallet kept somewhere else is named on both sides: tenrai-wallet daemon --file pool.vault --password-file pool.pass and tenrai-pool --wallet-token-file pool.vault.token.
On a testnet, start the daemon and the pool with --network testnet, and the pool also with the testnet's --chain-id. The pool checks at start that the daemon serves the node's chain. It refuses to start if the daemon serves another wallet than the one its database was created with.
5. Test it
tenrai-miner -a <address> --stratum stratum+tcp://127.0.0.1:28740 -w test --poolThe website on http://127.0.0.1:28772/ shows the worker once its first shares arrive; /miner/<address> shows the address in detail. A worker that has been offline for an hour no longer shows.
6. Go public
| Task | How |
|---|---|
| Website | nginx with TLS in front of --web-bind (default 127.0.0.1:28772), which must stay on loopback. Set proxy_set_header X-Real-IP $remote_addr; so rate limits count each visitor, and do not pass /metrics on. The pool repository ships a site file in deploy/nginx, including the WebSocket ports for web miners |
| Web miners | --pplns-listen-ws 127.0.0.1:28743 and --solo-listen-ws 127.0.0.1:28747 behind the same nginx, with --ws-origin set to the pages that may mine. The WebSocket location sets X-Real-IP again: nginx drops the server's proxy_set_header lines in a location that sets its own, and without that header the pool counts every web miner as 127.0.0.1, under one limit and never banned |
| Encrypted stratum | --pplns-listen-tls 0.0.0.0:28741 or --pplns-listen-v2-tls 0.0.0.0:28742; with --tls-cert and --tls-key for a certificate of your own |
| Firewall | Open the stratum ports, 443 and the node's P2P port 28700. Keep 28701, 28702, 28703 and 28772 closed |
| Abuse | --autoban bans addresses that send mostly invalid shares or bad requests |
| Service | Start node, daemon and pool with your service manager, in this order. tenrai-pool.json next to the program keeps the options |
The connect instructions on the website show the listener ports. --pplns-url and --solo-url replace them, for example when a proxy uses other ports.
Options
Every option also works in a JSON file, with the long option name as key. The pool reads tenrai-pool.json next to the program and creates it with the default settings on the first start; --config names another file. The command line wins.
Node and wallet
| Option | Default | What it does |
|---|---|---|
--network <NAME> | mainnet | mainnet, testnet or development. Sets the default ports, see Default ports |
--node <HOST[:PORT]> | 127.0.0.1:28702 | Mining listener of the node; a URL works too |
--node-api <HOST[:PORT]> | 127.0.0.1:28701 | API of the same node; a URL works too |
--chain-id <HEX> | mainnet | Expected chain ID. Required for a testnet or development node |
--node-identity <HEX> | Expected node identity. Required with a remote node, which also needs HTTPS | |
--wallet <HOST[:PORT]> | 127.0.0.1:28703 | Wallet daemon; a URL works too |
--wallet-token-file <FILE> | wallet.vault.token in this machine's wallet folder for the network | Token of the daemon. Without it and without that file the pool does not start, unless --no-payouts |
--no-wallet-check | off | Starts even when the daemon does not answer |
--pool-address <ADDR> | the daemon's | Pool address for a start without the daemon. Must be the daemon's receive address |
Stratum
| Option | Default | What it does |
|---|---|---|
--pplns-listen <IP[:PORT]> | 127.0.0.1:28740 | PPLNS port, version 1, unencrypted. Repeatable |
--solo-listen <IP[:PORT]> | 127.0.0.1:28744 | SOLO port, version 1, unencrypted. Repeatable |
--pplns-listen-tls, --solo-listen-tls | Version 1 over TLS, ports 28741 and 28745 unless given | |
--pplns-listen-v2-tls, --solo-listen-v2-tls | Version 2, always TLS, ports 28742 and 28746 unless given | |
--pplns-listen-ws, --solo-listen-ws | Version 1 over WebSocket for browser miners, behind a TLS proxy, ports 28743 and 28747 unless given | |
--ws-origin <ORIGIN> | any page | Web pages allowed to mine over WebSocket. Repeatable |
--no-v1 | off | No version 1 port at all; needs a version 2 port |
--tls-cert <FILE>, --tls-key <FILE> | stratum.crt and stratum.key beside the database | Certificate and key for the TLS and version 2 ports. Without them the pool creates a self-signed pair at its first start, for localhost, the listeners' addresses, --public-host and the connect URLs; miners pass the .crt as --tls-ca |
--max-clients <N> | 8192 | Connections in total |
--max-clients-per-ip <N> | 256 | Connections per IPv4 address or IPv6 /64 |
--idle-timeout <S> | 600 | Closes a connection that sends nothing for this long |
--tag <TEXT> | the miner's | Tag written into every block, e.g. the pool name |
--autoban | off | Bans addresses for bad requests or mostly invalid shares |
--ban-time <S> | 600 | Length of a ban |
Share difficulty
| Option | Default | What it does |
|---|---|---|
--start-difficulty <D> | 65536 | Share difficulty of a new worker |
--min-difficulty <D> | 50 | Lowest share difficulty, also for d= requests |
--max-difficulty <D> | network difficulty | Highest share difficulty |
--shares-per-block <N> | 16 | Keeps the share difficulty at most 1/N of the network difficulty |
--share-interval <S> | 20 | Seconds between two shares of one worker that vardiff aims for |
--vardiff-tolerance <%> | 25 | How far a worker may be off the aim before vardiff acts |
--vardiff-max-step <F> | 4 | Largest factor of one change |
--vardiff-history <N> | 64 | Shares the hashrate estimate uses at full weight |
TenQ
| Option | Default | What it does |
|---|---|---|
--tenq-mode full|light | full | light checks shares from the 32 MiB cache, far slower |
--no-tenq-prefetch | off | Builds the next epoch's dataset only when the epoch starts |
--verify-threads <N> | CPU count | Shares checked at the same time |
Rewards and payouts
| Option | Default | What it does |
|---|---|---|
--pplns-window <F> | 2 | PPLNS window in multiples of the network difficulty of the block |
--pplns-lookback <MIN> | 60 | Oldest share a window may reach |
--pplns-fee <%>, --solo-fee <%> | 2.5, 3 | Pool fee per block reward |
--fee-recipient <ADDR>=<%> | Pays this share of the fees to an address. Repeatable; the rest stays in the pool wallet | |
--no-payouts | off | Credits balances but sends nothing |
--payout-interval <MIN> | 15 | Minutes between payout rounds |
--min-payout <TRAI> | 1 | Smallest payout |
--payout-fee <FEE> | normal | minimum, normal, priority or a total in TRAI |
--payout-fee-max <TRAI> | Skips a round while a payout transaction would cost more | |
--payout-fee-payer pool|miners | pool | miners takes the fee from the payments in equal parts |
--payout-batch-size <N> | 7 | Recipients per transaction |
--payout-batches <N> | 20 | Transactions per round |
--compound-above <N> | 64 | Compounds the pool wallet above this many outputs; 0 turns it off |
Storage, website and logs
| Option | Default | What it does |
|---|---|---|
--db <FILE> | data/pool.sqlite | Database |
--keep-blocks <DAYS> | 2 | Keeps closed blocks for the lists; 0 forever |
--keep-blocks-max <N> | 10000 | Keeps at most this many closed blocks; 0 no limit |
--keep-block-splits <DAYS> | 0 | Keeps a block's PPLNS split for its block page; 0 drops it once the reward is credited |
--web-bind <IP[:PORT]> | 127.0.0.1:28772 | Website, API and metrics |
--no-web, --no-metrics | off | No website, or no /metrics |
--web-rate-limit <N> | 300 | Requests per minute and client; 0 turns the limit off |
--pool-name <TEXT> | Tenrai Pool | Name on the website |
--public-host <HOST> | the page's host | Host in the connect instructions |
--pplns-url, --solo-url <URL> | from the ports | Stratum URLs the connect instructions show instead. Repeatable |
--explorer-url <URL> | Tenrai Explorer | Explorer the website links to; empty for no links |
--stats-interval <S> | 60 | Seconds between status lines in the log |
-d, --log-file <FILE>, --no-color | Debug log, log file (moved to .old at 64 MiB), plain console |
Blocks
| Status | Meaning |
|---|---|
found | Stored, on its way to the node |
pending | Taken by the node, waiting for registration and its reward |
confirmed | Paid by a finalized cut; credited to the miners |
expired | Not registered before its window closed |
rejected | Refused by the node |
A finalized cut never changes, so a confirmed block is credited at once, without a maturity period.
Payouts
Each round pays the largest balances of at least --min-payout, --payout-batch-size miners per transaction. Every payout gets a request ID before it is sent; if the daemon's answer is unclear, the next round asks again under the same ID, so nothing is paid twice. Each address is checked again before a send.
API
| Route | Content |
|---|---|
/api/v1/pool | Pool, address, network, fees, ports, top miners |
/api/v1/blocks, /api/v1/payments | 25 blocks or payouts per page |
/api/v1/miners/{address} | Balance, workers, blocks and payouts of one miner |
/api/pools, /api/blocks, /api/miners/{address} | Miningcore format, for pool listing sites |
