Connection Lifecycle: From TCP Handshake to Steady-State Mining
The Big Picture
Section titled “The Big Picture”When an ASIC miner powers on and connects to a mining pool, it goes through a precise sequence of steps before it starts earning you money. Think of it like starting a new job: first you show up at the building (TCP connect), then you check in at the front desk (subscribe), show your employee badge (authorize), get told how hard your tasks should be (set_difficulty), receive your first assignment (notify), and then you start working (hashing and submitting).
This article walks through every step in that sequence, with real JSON-RPC messages you would see on the wire.
The Complete Connection Sequence
Section titled “The Complete Connection Sequence”Here is the full lifecycle, from cold start to steady-state mining:
MINER POOL | | | Step 1: TCP Connect | |-------- SYN --------------------------------->| |<------- SYN-ACK -----------------------------| |-------- ACK --------------------------------->| | | | Step 2 (optional): mining.configure | | (version rolling negotiation) | |-------- mining.configure -------------------->| |<------- configure result ---------------------| | | | Step 3: mining.subscribe | | (register for notifications, get extraNonce) | |-------- mining.subscribe -------------------->| |<------- subscribe result ---------------------| | | | Step 4: mining.authorize | | (authenticate the worker) | |-------- mining.authorize -------------------->| |<------- authorize result ---------------------| | | | Step 5: Pool sends initial parameters | |<------- mining.set_difficulty ----------------| |<------- mining.notify (first job) ------------| | | | Step 6: Steady-state mining loop | | [miner hashes... finds valid share] | |-------- mining.submit ----------------------->| |<------- submit result ------------------------| | | | [pool finds new block or adjusts difficulty] | |<------- mining.notify (new job) --------------| |<------- mining.set_difficulty (adjustment) ---| | | | [cycle continues indefinitely] | | |Let us walk through each step in detail.
Step 1: TCP Connection
Section titled “Step 1: TCP Connection”Before any Stratum messages are exchanged, the miner needs to establish a TCP connection to the pool. This is standard networking — a three-way handshake (SYN, SYN-ACK, ACK) that creates a reliable, bidirectional communication channel.
The miner connects to a specific host and port provided by the pool. For example:
stratum+tcp://pool.example.com:3333— standard unencryptedstratum+ssl://pool.example.com:3334— TLS-encrypted (some pools support this)
The port number matters. Many pools run different ports for different difficulty levels or features:
| Port | Common usage |
|---|---|
| 3333 | Standard difficulty (ASICs) |
| 3334 | High difficulty |
| 3335 | NiceHash or proxy connections |
| 25 | Low difficulty (older/smaller devices) |
Once the TCP connection is established, the miner and pool can begin exchanging JSON-RPC messages. Every message is a single line of JSON terminated by \n.
Step 2 (Optional): mining.configure
Section titled “Step 2 (Optional): mining.configure”Modern ASICs that support version rolling (a technique related to AsicBoost) will send a mining.configure message before subscribing. This negotiates which protocol extensions the miner and pool both support.
Miner sends:
{ "id": 1, "method": "mining.configure", "params": [ ["version-rolling"], {"version-rolling.mask": "1fffe000", "version-rolling.min-bit-count": 2} ]}Pool responds:
{ "id": 1, "result": { "version-rolling": true, "version-rolling.mask": "1fffe000" }, "error": null}The version-rolling.mask tells the miner which bits of the block version field it is allowed to modify. This gives the ASIC extra nonce space to search without touching the actual nonce or extraNonce fields. We will cover this in more detail in the article on mining.subscribe.
Step 3: mining.subscribe
Section titled “Step 3: mining.subscribe”This is where the miner registers itself with the pool. Think of it as checking into a hotel — you get a room number (session ID) and a key (extraNonce1).
Miner sends:
{ "id": 2, "method": "mining.subscribe", "params": ["bmminer/2.0.0", null]}The parameters are:
- User agent string — identifies the mining software and version
- Previous subscription ID —
nullfor a fresh connection, or a session ID if trying to resume
Pool responds:
{ "id": 2, "result": [ [ ["mining.set_difficulty", "deadbeef00000000"], ["mining.notify", "deadbeef00000001"] ], "a]f1c230", 4 ], "error": null}The response contains three critical pieces of information:
- Subscription pairs — tells the miner which notifications it is subscribed to (set_difficulty and notify), each with a subscription ID
- extraNonce1 — a hex string (
"a]f1c230") that uniquely identifies this miner’s session. Every share this miner submits will include this value. - extraNonce2 size — the number of bytes (4 in this example) the miner can use for its own nonce iteration
The extraNonce1 is assigned by the pool and must not be changed by the miner. The pool uses it to tell shares apart from different miners.
Step 4: mining.authorize
Section titled “Step 4: mining.authorize”Now the miner identifies itself — who is this worker, and which account should the mining rewards go to?
Miner sends:
{ "id": 3, "method": "mining.authorize", "params": ["username.worker1", "x"]}The parameters are:
- Worker name — typically in the format
account.workerorwallet_address.worker - Password — usually ignored by most pools (often just
"x"or""), though some pools use it for configuration
Pool responds (success):
{ "id": 3, "result": true, "error": null}Pool responds (failure):
{ "id": 3, "result": null, "error": [24, "Unauthorized worker", null]}If authorization fails, the miner cannot submit shares. Most implementations will close the connection and retry with correct credentials.
Step 5: Pool Sends Initial Parameters
Section titled “Step 5: Pool Sends Initial Parameters”Once the miner is subscribed and authorized, the pool immediately sends two critical pieces of information:
Difficulty Assignment
Section titled “Difficulty Assignment”{ "id": null, "method": "mining.set_difficulty", "params": [512]}This tells the miner the current share difficulty. The miner must find hashes that are below the target corresponding to this difficulty value. Note that id is null — this is a notification, not a request.
First Job
Section titled “First Job”{ "id": null, "method": "mining.notify", "params": [ "bf", "0000000000000000000354da8e026f48a0467f9d5290a7e30b86e1a80213e5fe", "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4b03a8960cfabe6d6d", "0100000001000000001976a914cb72e7463c3544121c5f37a8f3296c16c6a1289388ac00000000", [ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35" ], "20000000", "1705ae3a", "64a5f8c2", true ]}This is the actual work the miner needs to do. We will dissect every field of mining.notify in a later article, but for now, the key thing is: this message contains everything the miner needs to construct block headers and start hashing.
The last parameter (true) is the clean_jobs flag. When it is true, it means “throw away all your previous work and switch to this immediately.” For the first job, it is always true.
Step 6: Steady-State Mining Loop
Section titled “Step 6: Steady-State Mining Loop”Now the miner is fully operational. It enters a loop that continues for as long as the connection is alive:
-
Hash — The miner constructs block headers using the data from
mining.notifyand iterates through the nonce space, looking for hashes that meet the difficulty target. -
Submit shares — When the miner finds a hash below the target, it sends a
mining.submit:
{ "id": 4, "method": "mining.submit", "params": [ "username.worker1", "bf", "00000001", "64a5f8c2", "1a2b3c4d" ]}The pool responds with success or an error:
{"id": 4, "result": true, "error": null}-
Receive new jobs — The pool periodically sends new
mining.notifymessages. These can come for several reasons:- A new block was found on the network (clean_jobs = true)
- The pool wants to update the transaction set in the block template (clean_jobs = false)
- Enough time has passed that the timestamp should be refreshed
-
Difficulty adjustments — The pool monitors the miner’s share submission rate and adjusts difficulty via
mining.set_difficulty. If you are submitting shares too quickly, difficulty goes up. Too slowly, and it goes down.
This loop continues indefinitely — hours, days, weeks — until something breaks the connection.
What Happens When the Connection Drops
Section titled “What Happens When the Connection Drops”Connections drop. It is a fact of life. Power blips, router reboots, pool maintenance, ISP hiccups — there are countless reasons a TCP connection might die. Here is what a well-behaved miner does:
Detection
Section titled “Detection”The miner detects the drop in one of several ways:
- TCP reset or FIN received from the pool
- Write to socket fails (broken pipe)
- No data received for too long (the miner’s own keepalive timeout)
Some miners send periodic mining.ping messages (or any dummy request) to detect dead connections. If the pool does not respond within a timeout, the connection is considered dead.
Reconnection Strategy
Section titled “Reconnection Strategy”Connection drops | v Wait (backoff period) | v Try primary pool -----> Success? ---> Re-subscribe + Re-authorize | | | Fail v v Resume mining with new Try backup pool extraNonce1 and new jobs | | Fail v Increase backoff (exponential: 1s, 2s, 4s, 8s... up to 60s) | v Try primary pool again [loop]Key points about reconnection:
-
Re-subscribe is mandatory. After reconnecting, the miner must go through
mining.subscribeandmining.authorizeagain. The pool may assign a differentextraNonce1. -
Old work is invalid. Any shares the miner was working on from the previous session are worthless. The miner must wait for new
mining.notifymessages. -
Exponential backoff. A good miner does not hammer the pool with reconnection attempts. It waits 1 second, then 2, then 4, then 8, and so on, capping at around 60 seconds. This prevents overwhelming the pool during an outage.
-
Backup pools. Most mining firmware lets you configure multiple pool addresses. If the primary pool is unreachable, the miner falls over to the secondary, and then the tertiary. It periodically checks if the primary is back.
Session Resumption Example
Section titled “Session Resumption Example”Miner attempts to resume:
{ "id": 1, "method": "mining.subscribe", "params": ["bmminer/2.0.0", "previous_subscription_id_here"]}If the pool still has the session in memory, it may return the same extraNonce1, allowing the miner to resume without losing its place. If the session has expired or the pool does not support resumption, it simply assigns a new session.
Timing: How Fast Does This All Happen?
Section titled “Timing: How Fast Does This All Happen?”On a typical connection, the entire handshake — from TCP connect to receiving the first job — takes well under one second. Here is a realistic timeline:
| Step | Time from start |
|---|---|
| TCP handshake (SYN/SYN-ACK/ACK) | ~20-50 ms |
| mining.configure (if used) | ~50-100 ms |
| mining.subscribe + response | ~80-150 ms |
| mining.authorize + response | ~100-200 ms |
| mining.set_difficulty received | ~120-220 ms |
| mining.notify received | ~130-230 ms |
| Miner starts hashing | ~150-250 ms |
These numbers assume a stable internet connection with ~20ms latency to the pool. In practice, most ASICs are hashing within 200-300 milliseconds of connecting. For a machine that runs 24/7, this startup cost is negligible.
A Real-World Packet Capture
Section titled “A Real-World Packet Capture”To tie this all together, here is what the actual bytes on the wire might look like for a complete connection setup. Each line is one JSON-RPC message (newline-delimited):
Miner —> Pool:
{"id":1,"method":"mining.configure","params":[["version-rolling"],{"version-rolling.mask":"1fffe000","version-rolling.min-bit-count":2}]}Pool —> Miner:
{"id":1,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"},"error":null}Miner —> Pool:
{"id":2,"method":"mining.subscribe","params":["bmminer/2.0.0",null]}Pool —> Miner:
{"id":2,"result":[[["mining.set_difficulty","1"],["mining.notify","1"]],"abcd1234",4],"error":null}Miner —> Pool:
{"id":3,"method":"mining.authorize","params":["myaccount.worker1","x"]}Pool —> Miner:
{"id":3,"result":true,"error":null}Pool —> Miner:
{"id":null,"method":"mining.set_difficulty","params":[512]}Pool —> Miner:
{"id":null,"method":"mining.notify","params":["job1","prevhash...","cb1...","cb2...","merkle...","20000000","1a0ffff0","64b1c3a5",true]}At this point, the miner has everything it needs and begins hashing. The first share submission might come seconds or minutes later, depending on the miner’s hashrate and the share difficulty.
Multiple Workers, One Connection
Section titled “Multiple Workers, One Connection”An interesting feature of Stratum is that a single TCP connection can handle multiple workers. This is commonly used by mining proxies — software that sits between a farm of ASICs and the pool. The proxy maintains one connection to the pool but manages dozens or hundreds of ASICs locally.
The flow looks like this:
{"id":3,"method":"mining.authorize","params":["account.worker1","x"]}{"id":4,"method":"mining.authorize","params":["account.worker2","x"]}{"id":5,"method":"mining.authorize","params":["account.worker3","x"]}Each worker is authorized independently, and shares submitted with mining.submit specify which worker found them. The pool tracks hashrate and earnings per worker.
Common Connection Problems
Section titled “Common Connection Problems”Here are issues you might encounter and what they mean:
“Connection refused” — The pool server is not listening on that port. Check the hostname, port, and that the pool is actually online.
“Connection timeout” — Your network cannot reach the pool. Could be a firewall, ISP issue, or the pool is overloaded. Try a different pool address or port.
Authorization failure — You are connecting fine but the pool rejects your worker name. Double-check your account name, wallet address, or worker format. Some pools require you to create workers in a web dashboard first.
Immediate disconnection after subscribe — This sometimes happens if you are connecting to the wrong port (for example, an SSL port without TLS, or vice versa).
Stale shares after reconnection — If you reconnect and immediately submit shares from the old session, the pool will reject them. Always wait for fresh mining.notify messages after reconnecting.
Summary
Section titled “Summary”The Stratum connection lifecycle is a well-defined sequence:
- TCP connect to the pool’s host and port
- mining.configure (optional) to negotiate protocol extensions like version rolling
- mining.subscribe to register and receive extraNonce1 and extraNonce2 size
- mining.authorize to authenticate the worker
- mining.set_difficulty and mining.notify from the pool to set up initial work
- Steady-state loop: receive jobs, hash, submit shares, receive difficulty adjustments
When the connection drops, the miner reconnects with exponential backoff and goes through the entire handshake again. Some pools support session resumption for faster recovery.
In the next article, we will take a deep dive into mining.subscribe and mining.authorize — examining every field, every edge case, and how extraNonce assignment actually works.