EIP-1571 - EthereumStratum/2.0.0

Created 2018-11-09
Status Stagnant
Category Interface
Type Standards Track
Authors

Abstract

This draft contains the guidelines to define a new standard for the Stratum protocol used by Ethereum miners to communicate with mining pool servers.

Conventions

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119. The definition mining pool server, and it's plural form, is to be interpreted as work provider and later in this document can be shortened as pool or server. The definition miner(s), and it's plural form, is to be interpreted as work receiver/processor and later in this document can be shortened as miner or client.

Rationale

Ethereum does not have an official Stratum implementation yet. It officially supports only getWork which requires miners to constantly pool the work provider. Only recently go-ethereum have implemented a push mechanism to notify clients for mining work, but whereas the vast majority of miners do not run a node, it's main purpose is to facilitate mining pools rather than miners. The Stratum protocol on the other hand relies on a standard stateful TCP connection which allows two-way exchange of line-based messages. Each line contains the string representation of a JSON object following the rules of either JSON-RPC 1.0 or JSON-RPC 2.0. Unfortunately, in absence of a well defined standard, various flavours of Stratum have bloomed for Ethereum mining as a derivative work for different mining pools implementations. The only attempt to define a standard was made by NiceHash with their EthereumStratum/1.0.0 implementation which is the main source this work inspires from. Mining activity, thus the interaction among pools and miners, is at it's basics very simple, and can be summarized with "please find a number (nonce) which coupled to this data as input for a given hashing algorithm produces, as output, a result which is below a certain target". Other messages which may or have to be exchanged among parties during a session are needed to support this basic concept. Due to the simplicity of the subject, the proponent, means to stick with JSON formatted objects rather than investigating more verbose solutions, like for example Google's Protocol Buffers which carry the load of strict object definition.

Stratum design flaws

The main Stratum design flaw is the absence of a well defined standard. This implies that miners (and mining software developers) have to struggle with different flavours which make their life hard when switching from one pool to another or even when trying to "guess" which is the flavour implemented by a single pool. Moreover all implementations still suffer from an excessive verbosity for a chain with a very small block time like Ethereum. A few numbers may help understand. A normal mining.notify message weigh roughly 240 bytes: assuming the dispatch of 1 work per block to an audience of 50k connected TCP sockets means the transmission of roughly 1.88TB of data a month. And this can be an issue for large pools. But if we see the same figures the other way round, from a miner's perspective, we totally understand how mining decentralization is heavily affected by the quality of internet connections.

Sources of inspiration

Specification

The Stratum protocol is an instance of JSON-RPC-2.0. The miner is a JSON-RPC client, and the server is a JSON-RPC server. All communications exist within the scope of a session. A session starts at the moment a client opens a TCP connection to the server till the moment either party do voluntary close the very same connection or it gets broken. Servers MAY support session resuming if this is initially negotiated (on first session handshaking) between the client and the server. During a session all messages exchanged among server and client are line-based which means all messages are JSON strings terminated by ASCII LF character (which may also be denoted as \n in this document). The LF character MUST NOT appear elsewhere in a message. Client and server implementations MUST assume that once they read a LF character, the current message has been completely received and can be processed. Line messages are of three types:

During a session both parties CAN exchange messages of the above depicted three types.

JSON-RPC-2.0 Compliances

As per JSON-RPC-2.0 specification requests and responses differ from notifications by the identifier (id) member in the JSON object:

JSON-RPC-2.0 Defiances

In order to get the most concise messages among parties of a session/conversation this implementation enforces the following defiances:

Conventions

Requests

The JSON representation of request object is made of these parts:

Responses

The JSON representation of response object is made of these parts:

You'll notice here some differences with standard JSON-RPC-2.0. Namely the result member is not always required. Basically a response like this:

{"id": 2}

means "request received and processed correctly with no data to send back".

To better clarify the concept and clear the field of free interpretations let's take another example of wrong response

{"id": 2, "result": false}

This response syntax leaves room to many interpretations: is it an error? is false the legit response value to the issued request?

For this reason responses, we reiterate, MUST BE of two types:

The latter deserves a better explanation: failure responses can be distinguished by a severity degree. Example 1 : a client submits a solution and server rejects it cause it's not below target. Server MUST respond like this;

{
  "id": 31,
  "error": {
      "code": 406,
      "message" : "Bad nonce"
  }
}

Example 2 : a client submits a solution and server accepts it but it accounts the share as stale. Server MUST respond like this;

{
  "id": 31,
  "error": {
      "code": 202,
      "message" : "Stale"
  }
}

Example 3 : a client submits an authorization request specifying an invalid workername. Server authorizes the account but rejects worker name. Server MUST respond like this;

{
  "id": 1,
  "error": {
      "code": 215,
      "message" : "Invalid Worker Name"
  }
}

Example 1 depicts the condition of a severe failure while Example 2 and 3 depict a situation where the request has been accepted and processed properly but the result MAY NOT be what expected by the client. It's up to the client to evaluate the severity of the error and decide whether to proceed or not.

Using proper error codes pools may properly inform miners of the condition of their requests. Error codes MUST honor this scheme:

Notifications

A notification message has the very same representation of a request with the only difference the id member MUST NOT be present. This means the issuer is not interested nor expects any response to this message. It's up to the receiver to take actions accordingly. For instance the receiver MAY decide to execute the method, or, in case of errors or methods not allowed, drop the connection thus closing the session.

Error member

As seen above a response MAY contain an error member. When present this member MUST be an Object with:

Protocol Flow

Session Handling - Hello

~~One of the worst annoyances until now is that server, at the very moment of socket connection, does not provide any useful information about the stratum flavour implemented. This means the client has to start a conversation by iteratively trying to connect via different protocol flavours. This proposal amends the situation making mandatory for the server to advertise itself to the client. When a new client connects to the server, the server MUST send a mining.hello notification :~~

It's been noted that charging the server of the duty to advertise itself as first message of the conversation could potentially be harmful in respect of traffic amplification attacks using spoofed IP addresses or in traditional DDos attacks where an attacker need to spend very little resources to force the server to send a large packet back. For this reason the duty of first advertisement is kept on client which will issue a mining.hello request like this:

{
  "id" : 0,
  "method": "mining.hello", 
  "params": 
  { 
    "agent": "ethminer-0.17",
    "host" : "somemininigpool.com",
    "port" : "4d2",
    "proto": "EthereumStratum/2.0.0"
  }
}

The params member object has these mandatory members:

The rationale behind sending host and port is it enables virtual hosting, where virtual pools or private URLs might be used for DDoS protection, but that are aggregated on Stratum server backends. As with HTTP, the server CANNOT trust the host string. The port is included separately to parallel the client.reconnect method (see below).

If the server is prepared to start/resume a session with such requirements it MUST reply back with a response like this:

{
  "id" : 0,
  "result": 
  { 
    "proto" : "EthereumStratum/2.0.0",
    "encoding" : "gzip",
    "resume" : "1",
    "timeout" : "b4",
    "maxerrors" : "5",
    "node" : "Geth/v1.8.18-unstable-f08f596a/linux-amd64/go1.10.4"
  } 
}

Where the result is an object made of 5 mandatory members

When the server replies back with "encoding" : "gzip" to the client, both parties MUST gzip compress all next messages. In case the client is not capable of compression it MUST close the connection immediately. Should the server, after this reply, receive other messages as plain text, it MUST close the connection.

Eventually the client will continue with mining.subscribe (further on descripted)

Otherwise, in case of errors or rejection to start the conversation, the server MAY reply back with an error giving the other party useful information or, at server's maintainers discretion, abruptly close the connection.

{
  "id" : 0,
  "error": 
  { 
      "code": 400,
      "message" : "Bad protocol request"
  } 
}

or

{
  "id" : 0,
  "error": 
  { 
      "code": 403,
      "message" : "Forbidden - Banned IP address"
  } 
}

The above two JSON error values are only samples Eventually the server will close the connection.

Why a pool should advertise the node's version? It's a matter of transparency : miners should know whether or not pool have upgraded to latest patches/releases for node's software.

Session Handling - Bye

Disconnection are not gracefully handled in Stratum. Client disconnections from pool may be due to several errors and this leads to waste of TCP sockets on server's side which wait for keepalive timeouts to trigger. A useful notification is mining.bye which, once processed, allows both parties of the session to stop receiving and gracefully close TCP connections

{
  "method": "mining.bye"
}

The party receiving this message aknowledges the other party wants to stop the conversation and closes the socket. The issuer will close too. The explicit issuance of this notification implies the session gets abandoned so no session resuming will be possible even on server which support session-resuming. Client reconnecting to the same server which implements session resuming SHOULD expect a new session id and MUST re-authorize all their workers.

Session Handling - Session Subscription

After receiving the response to mining.hello from server, the client, in case the server does support session resuming MAY request to resume a previously interrupted session with mining.subscribe request:

{
  "id": 1,
  "method": "mining.subscribe", 
  "params": "s-12345"
}

where params is the id of the session the client wants to resume.

Otherwise, if client wants to start a new session OR server does not support session resuming, the request of subscription MUST omit the params member:

{
  "id": 1,
  "method": "mining.subscribe"
}

Session Handling - Response to Subscription

A server receiving a client session subscription MUST reply back with

{
  "id": 1,
  "result": "s-12345"
}

A server receiving a subscription request with params being a string holding the session id. This cases may apply

A server implementing session-resuming MUST cache:

Servers MAY drop entries from the cache on their own schedule. It's up to server to enforce session validation for same agent and/or ip.

A client which successfully subscribes and resumes session (the session value in server response is identical to session value requested by client on mining.subscribe) CAN omit to issue the authorization request for it's workers.

Session Handling - Noop

There are cases when a miner struggles to find a solution in a reasonable time so it may trigger the timeout imposed by the server in case of no communications (the server, in fact, may think the client got disconnected). To mitigate the problem a new method mining.noop(with no additional parameters) may be requested by the client.

{
  "id": 50,
  "method": "mining.noop"
}

Session Handling - Reconnect

Under certain circumstances the server may need to free some resources and or to relocate miners to another machine. Until now the only option for servers was to abruptly close the connection. On the miner's side this action is interpreted as a server malfunction and they, more often than not, switch to a failover pool. The implementation of the notification mining.reconnect helps client to better merge with logic of handling of large mining pools.

{
  "method": "mining.reconnect",
  "params": {
      "host": "someotherhost.com",
      "port": "d80",
      "resume": "1"
  }
}

This notification is meant only from servers to clients. Should a server receive such a notification it will simply ignore it. After the notification has been properly sent, the server is ALLOWED to close the connection, while the client will take the proper actions to reconnect to the suggested end-point. The host member in params object SHOULD report a host DNS name and not an IP address: TLS encrypted connections require to validate the CN name in the certificate which, 99% of the cases, is a host name. The third member resume of the params object sets whether or not the receiving server is prepared for session resuming. After this notification has been issued by the server, the client should expect no further messages and MUST disconnect.

Workers Authorization

The miner MUST authorize at least one worker in order to begin receiving jobs and submit solutions or hashrates. The miner MAY authorize multiple workers in the same session. The server MUST allow authorization for multiple workers within a session and MUST validate at least one authorization from the client before starting to send jobs. A worker is a tuple of the address where rewards must be credited coupled with identifier of the machine actually doing the work. For Ethereum the most common form is <account>.<MachineName>. The same account can be bound to multiple machines. For pool's allowing anonymous mining the account is the address where rewards must be credited, while, for pools requiring registration, the account is the login name. Each time a solution is submitted by the client it must be labelled with the Worker identifier. It's up to server to keep the correct accounting for different addresses.

The syntax for the authorization request is the following:

{
  "id": 2,
  "method": "mining.authorize", 
  "params": ["<account>[.<MachineName>]", "password"]
}

params member must be an Array of 2 string elements. For anonymous mining the "password" can be any string value or empty but not null. Pools allowing anonymous mining will simply ignore the value. The server MUST reply back either with an error or, in case of success, with

{
  "id": 2,
  "result": "w-123"
}

Where the result member is a string which holds an unique - within the scope of the session - token which identifies the authorized worker. For every further request issued by the client, and related to a Worker action, the client MUST use the token given by the server in response to an mining.authorize request. This reduces the number of bytes transferred for solution and /or hashrate submission.

If client is resuming a previous session it CAN omit the authorization request for it's workers and, in this case, MUST use the tokens assigned in the originating session. It's up to the server to keep the correct map between tokens and workers. The server receiving an authorization request where the credentials match previously authorized ones within the same session MUST reply back with the previously generated unique token.

Prepare for mining

A lot of data is sent over the wire multiple times with useless redundancy. For instance the seed hash is meant to change only every 30000 blocks (roughly 5 days) while fixed-diff pools rarely change the work target. Moreover pools must optimize the search segments among miners trying to assign to every session a different "startNonce" (AKA extraNonce). For this purpose the notification method mining.set allows to set (on miner's side) only those params which change less frequently. The server will keep track of seed, target and extraNonce at session level and will push a notification mining.set whenever any (or all) of those values change to the connected miner.

{
  "method": "mining.set", 
  "params": {
      "epoch" : "dc",
      "target" : "0112e0be826d694b2e62d01511f12a6061fbaec8bc02357593e70e52ba",
      "algo" : "ethash",
      "extranonce" : "af4c"
  }
}

At the beginning of each session the server MUST send this notification before any mining.notify. All values passed by this notification will be valid for all NEXT jobs until a new mining.set notification overwrites them. Description of members is as follows:

Whenever the server detects that one, or two, or three or four values change within the session, the server will issue a notification with one, or two or three or four members in the param object. For this reason on each new session the server MUST pass all four members. As a consequence the miner is instructed to adapt those values on next job which gets notified. The new algo member is defined to be prepared for possible presence of algorithm variants to ethash, namely ethash1a or ProgPow. Pools providing multicoin switching will take care to send a new mining.set to miners before pushing any job after a switch. The client which can't support the data provided in the mining.set notification MAY close connection or stay idle till new values satisfy it's configuration (see mining.noop). All client's implementations MUST be prepared to accept new extranonces during the session: unlike in EthereumStratum/1.0.0 the optional client advertisement mining.extranonce.subscribe is now implicit and mandatory.

The miner receiving the extranonce MUST initialize the search segment for next job resizing the extranonce to a hex of 16 bytes thus appending as many zeroes as needed. Extranonce "af4c" means "search segment of next jobs starts from 0xaf4c000000000000" If extranonce is valued to an empty string, or it's never been set within the session scope, the client is free pick any starting point of it's own search segment on subsequent mining.notify jobs.

A detail of "extranonce"

Miners connected to a pool might likely process the very same nonces thus wasting a lot of duplicate jobs. A nonce is any valid number which, applied to algorithm and job specifications, produces a result which is below a certain target. For every job pushed by server to client(s) there are 2^64 possible nonces to test.

To be noted that:

Every "test" over a number is called a hash. Assuming a miner should receive a job for each block and considering the actual average block time of 15 seconds that would mean a miner should try

  ( 2^64 / 15 ) / 1T ~ 1,229,782.94 TeraHashes per second

This computation capacity is well beyond any miner on the market (including ASICs). For this reason single miners can process only small chunks (segments) of this humongous range. The way miners pick the segments to search on is beyond the scope of this work. Fact is as miners are not coordinated there is no knowledge - for a single miner - of segments picked by other miners. Extranonce concept is here to mitigate this possibility of duplicate jobs charging the server (the work provider) to give miners, at the maximum possible extent, different segments to search on.

Giving the above assumptions we can depict a nonce as any number in the hex range:

  Min 0x0000000000000000
  Max 0xffffffffffffffff

the prefix 0x is voluntarily inserted here only to give a better visual representation.

The extranonce is, at it's basics, the message of the server saying the client "I give you the first number to start search from". More in detail the extranonce is the leftmost part of that number. Assume a pool notifies the client the usage of extranonce ab5d this means the client will see it's search segment narrowed as

  Min 0xab5d000000000000
  Max 0xab5dffffffffffff

Pushing an extranonce of 4 bytes (like in the example) will give pool the possibility to separate segment 65535 different miners ( or if you prefer 0xffff miners ) while leaving the miner still a segment of 2^48 possible nonces to search on. Recalculating, as above, the computation capacity needed to search this segment we get

  ( 2^48 / 15 ) / 1T ~ 18.76 TeraHashes per second

Which is still a wide segment where miners can randomly (or using other ergodic techniques) pick their internal search segments.

Extranonce MUST be passed with all relevant bytes (no omission of left zeroes) for a specific reason. Assume an extranonce of "01ac" : it has the same decimal value of "1ac" but the number of bytes changes thus changing available search segment

  When "01ac"               When "1ac"
  Segment is                Segment is
  Min  0x01ac000000000000   Min  0x1ac0000000000000
  Max  0x01acffffffffffff   Max  0x1acfffffffffffff

As you can see resulting segments are quite different

This all said pools (server), when making use of extranonce, MUST observe a maximum length of 6 bytes (hex).

Jobs notification

When available server will dispatch jobs to connected miners issuing a mining.notify notification.

{
  "method": "mining.notify", 
  "params": [
      "bf0488aa",
      "6526d5"
      "645cf20198c2f3861e947d4f67e3ab63b7b2e24dcc9095bd9123e7b33371f6cc",
      "0"
  ]
}

params member is made of 4 mandatory elements:

Solution submission

When a miner finds a solution for a job he is mining on it sends a mining.submit request to server.

{
  "id": 31,
  "method": "mining.submit", 
  "params": [
      "bf0488aa",
      "68765fccd712",
      "w-123"
  ]
}

First element of params array is the jobId this solution refers to (as sent in the mining.notify message from the server). Second element is the miner nonce as hex. Third element is the token given to the worker previous mining.authorize request. Any mining.submit request bound to a worker which was not successfully authorized - i.e. the token does not exist in the session - MUST be rejected.

You'll notice in the sample above the miner nonce is only 12 bytes wide (should be 16). Why? That's because in the previous mining.set the server has set an extranonce of af4c. This means the full nonce is af4c68765fccd712 In presence of extranonce the miner MUST submit only the chars to append to the extranonce to build the final hex value. If no extranonce is set for the session or for the work the miner MUST send all 16 bytes.

It's server duty to keep track of the tuples job ids <-> extranonces per session.

When the server receives this request it either responds success using the short form

{"id": 31}

or, in case of any error or condition with a detailed error object

{
  "id": 31,
  "error": {
      "code": 404,
      "message" : "Job not found"
  }
}

Client should treat errors as "soft" errors (stales) or "hard" (bad nonce computation, job not found etc.). Errors in 5xx range are server errors and suggest the miner to abandon the connection and switch to a failover.

Hashrate

Most pools offer statistic information, in form of graphs or by API calls, about the calculated hashrate expressed by the miner while miners like to compare this data with the hashrate they read on their devices. Communication about parties of these information have never been coded in Stratum and most pools adopt the method from getWork named eth_submitHashrate. In this document we propose an official implementation of the mining.hashrate request. This method behaves differently when issued from client or from server.

Client communicates it's hashrate to server.

{
  "id" : 16,
  "method": "mining.hashrate",
  "params": [
      "500000",
      "w-123"
      ]
}

where params is an array made of two elements: the first is a hexadecimal string representation (32 bytes) of the hashrate the miner reads on it's devices and the latter is the authorization token issued to worker this hashrate is refers to (see above for mining.authorization). Server MUST respond back with either an aknowledgment message

{"id": 16 }

Optionally the server can reply back reporting it's findings about calculated hashrate for the same worker.

{
  "id": 16,
  "result" : [
      "4f0000",
      "w-123"
      ]
}

In case of errors - for example when the client submits too frequently - with

{
  "id": 16,
  "error" : {
    "code": 220,
    "message": "Enhance your calm. Too many requests"
  }
}

Server communicates hashrate to client

Optionally the server can notify client about it's overall performance (according to schedule set on server) with a mining.hashrate notification composed like this

{
  "method": "mining.hashrate",
  "params": {
      "interval": 60,
      "hr": "500000",
      "accepted": [3692,20],
      "rejected": 0,
  }
}

Where params is an object which holds these members for values of the whole session:

The client will eventually take internal actions to reset/restart it's workers.

Copyright

Copyright and related rights waived via CC0.