Skip to content

Commit

Permalink
- stop mining if no new jobs after timeout, at example because of net…
Browse files Browse the repository at this point in the history
…work failure. will be continued with new job

- proxy connections state via browser
- added pool servers in config: US2, HK, CN, SG, AU
- removed default pool wallet
- now 3 failover servers
- new parameter "coin" for ETH or EXP
- fixed some bugs with stratum connections
- remove email from command line of miner
- show hashrate of miners
  • Loading branch information
Atrides committed Mar 8, 2016
1 parent 8a2e2b9 commit d993fc8
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 72 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Description

This is Stratum Proxy for Ethereum-pools (RPCv2) using asynchronous networking written in Python Twisted.
This is Stratum Proxy for Ethereum based pools (RPCv2) using asynchronous networking written in Python Twisted.
Originally developed for DwarfPool http://dwarfpool.com/eth

**NOTE:** This fork is still in development. Some features may be broken. Please report any broken features or issues.
Expand Down Expand Up @@ -47,9 +47,21 @@ Originally developed for DwarfPool http://dwarfpool.com/eth
* ./ethminer --farm-recheck 200 -G -F http://127.0.0.1:8080/rig1


#External script to restart proxy (made by rain)

* https://paste.ubuntu.com/15327007/


# Proxy working check

* To check that proxy works open in browser http://127.0.0.1:8080/ (or your changed ip and port from config)
* If you see "Ethereum stratum proxy" and some infos about connections.
* If not then mostly case that you have application running on this port, at example Antivirus.


#Donations

* ETH: 0xb7302f5988cd483db920069a5c88f049a3707e2f
* ETH: 0xea7263feb7d8a8ab0a11eedd8f1ce04412ab0820


#Requirements
Expand Down
42 changes: 31 additions & 11 deletions eth-proxy.conf
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
###
# Command line for miners:
# Examples of command line for miners:
#
# ethminer.exe -G -F http://HOST:PORT/
# ethminer.exe -G -F http://HOST:PORT/rig1
# ethminer.exe --farm-recheck 200 -G -F http://HOST:PORT/
# ethminer.exe --farm-recheck 300 -G -F http://HOST:PORT/rig1
#
# ethminer.exe -G -F http://127.0.0.1:8080/
# ethminer.exe -G -F http://192.168.0.33:8080/rig1
# ethminer.exe --farm-recheck 100 -G -F http://192.168.0.33:8080/rig1
#
# farm-recheck parameter is very individual. Just test different values.
#
# You can submit shares without workername or
# You can provide workername:
# - with url like "/rig1"
# - or use automatically numbering(integer) based on IP of miner
#
# Servers:
# EU-Server: eth-eu.dwarfpool.com (France)
# US-Server: eth-us.dwarfpool.com (Montreal,Canada)
# RU-Server: eth-ru.dwarfpool.com (Moscow)
# EU-Server: eth-eu.dwarfpool.com (France)
# US-Server: eth-us.dwarfpool.com (EastCoast: Montreal,Canada)
# US-Server: eth-us2.dwarfpool.com (WestCoast: Las Vegas)
# RU-Server: eth-ru.dwarfpool.com (Moscow)
# HK-Server: eth-hk.dwarfpool.com (Hong-Kong)
# CN-Server: eth-cn.dwarfpool.com (Shanghai)
# SG-Server: eth-sg.dwarfpool.com (Singapore)
# AU-Server: eth-au.dwarfpool.com (Melbourne)
#
###

# Select Ethereum ETH or Expanse EXP
COIN = "ETH"

# Host and port for your workers
HOST = "0.0.0.0"
PORT = 8080

# Coin address where money goes
WALLET = "0x2a65aca4d5fc5b5c859090a6c34d164135398226"
WALLET = "XXXXXX"

# To donate please use wallet "0xea7263feb7d8a8ab0a11eedd8f1ce04412ab0820"

# It's useful for individually monitoring and statistic
ENABLE_WORKER_ID = True
ENABLE_WORKER_ID = False

# On DwarfPool you have option to monitor your workers via email.
# If WORKER_ID is enabled, you can monitor every worker/rig separately.
Expand All @@ -40,8 +52,16 @@ POOL_PORT = 8008

# Failover pool
POOL_FAILOVER_ENABLE = True
POOL_HOST_FAILOVER = "eth-us.dwarfpool.com"
POOL_PORT_FAILOVER = 8008

POOL_HOST_FAILOVER1 = "eth-ru.dwarfpool.com"
POOL_PORT_FAILOVER1 = 8008

POOL_HOST_FAILOVER2 = "eth-us.dwarfpool.com"
POOL_PORT_FAILOVER2 = 8008

POOL_HOST_FAILOVER3 = "eth-hk.dwarfpool.com"
POOL_PORT_FAILOVER3 = 8008


# Logging
LOG_TO_FILE = True
Expand Down
60 changes: 44 additions & 16 deletions eth-proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import time
import os
import sys
import socket

from stratum import settings
Expand All @@ -12,7 +13,7 @@
if __name__ == '__main__':
if len(settings.WALLET)!=42 and len(settings.WALLET)!=40:
log.error("Wrong WALLET!")
quit()
sys.exit()
settings.CUSTOM_EMAIL = settings.MONITORING_EMAIL if settings.MONITORING_EMAIL and settings.MONITORING else ""

from twisted.internet import reactor, defer, protocol
Expand All @@ -26,7 +27,6 @@
from mining_libs import client_service
from mining_libs import jobs
from mining_libs import version
from mining_libs.jobs import Job

def on_shutdown(f):
'''Clean environment properly'''
Expand All @@ -42,7 +42,10 @@ def ping(f):
return
try:
yield (f.rpc('eth_getWork', [], ''))
reactor.callLater(60, ping, f)
if f.is_failover:
reactor.callLater(30, ping, f)
else:
reactor.callLater(5, ping, f)
except Exception:
pass

Expand All @@ -53,18 +56,18 @@ def on_connect(f):
f.is_connected = True
f.remote_ip = f.client._get_ip()
#reactor.callLater(30, f.client.transport.loseConnection)

# Hook to on_connect again
f.on_connect.addCallback(on_connect)

# Get first job and user_id
debug = "_debug" if settings.DEBUG else ""
initial_job = (yield f.rpc('eth_submitLogin', [settings.WALLET, settings.CUSTOM_EMAIL], 'Proxy_'+version.VERSION+debug))

reactor.callLater(0, ping, f)

defer.returnValue(f)

def on_disconnect(f):
'''Callback when proxy get disconnected from the pool'''
log.info("Disconnected from Stratum pool at %s:%d" % f.main_host)
Expand All @@ -82,28 +85,52 @@ def main():
f = SocketTransportClientFactory(settings.POOL_HOST, settings.POOL_PORT,
debug=settings.DEBUG, proxy=None,
event_handler=client_service.ClientMiningService)
f.is_failover = False

ff = None
f1 = None
f2 = None
f3 = None
if settings.POOL_FAILOVER_ENABLE:
log.warning("Trying to connect to failover Stratum pool at %s:%d" % (settings.POOL_HOST_FAILOVER, settings.POOL_PORT_FAILOVER))
ff = SocketTransportClientFactory(settings.POOL_HOST_FAILOVER, settings.POOL_PORT_FAILOVER,
log.warning("Trying to connect to failover Stratum pool-1 at %s:%d" % (settings.POOL_HOST_FAILOVER1, settings.POOL_PORT_FAILOVER1))
f1 = SocketTransportClientFactory(settings.POOL_HOST_FAILOVER1, settings.POOL_PORT_FAILOVER1,
debug=settings.DEBUG, proxy=None,
event_handler=client_service.ClientMiningService)
ff.is_failover = True
f1.is_failover = True

job_registry = jobs.JobRegistry(f,ff)
log.warning("Trying to connect to failover Stratum pool-2 at %s:%d" % (settings.POOL_HOST_FAILOVER2, settings.POOL_PORT_FAILOVER2))
f2 = SocketTransportClientFactory(settings.POOL_HOST_FAILOVER2, settings.POOL_PORT_FAILOVER2,
debug=settings.DEBUG, proxy=None,
event_handler=client_service.ClientMiningService)
f2.is_failover = True

log.warning("Trying to connect to failover Stratum pool-3 at %s:%d" % (settings.POOL_HOST_FAILOVER3, settings.POOL_PORT_FAILOVER3))
f3 = SocketTransportClientFactory(settings.POOL_HOST_FAILOVER3, settings.POOL_PORT_FAILOVER3,
debug=settings.DEBUG, proxy=None,
event_handler=client_service.ClientMiningService)
f3.is_failover = True

job_registry = jobs.JobRegistry(f,f1,f2,f3)
client_service.ClientMiningService.job_registry = job_registry
client_service.ClientMiningService.reset_timeout()

f.on_connect.addCallback(on_connect)
f.on_disconnect.addCallback(on_disconnect)
# Cleanup properly on shutdown
reactor.addSystemEventTrigger('before', 'shutdown', on_shutdown, f)
if ff:
ff.on_connect.addCallback(on_connect)
ff.on_disconnect.addCallback(on_disconnect)
reactor.addSystemEventTrigger('before', 'shutdown', on_shutdown, ff)
if f1:
f1.on_connect.addCallback(on_connect)
f1.on_disconnect.addCallback(on_disconnect)
reactor.addSystemEventTrigger('before', 'shutdown', on_shutdown, f1)

if f2:
f2.on_connect.addCallback(on_connect)
f2.on_disconnect.addCallback(on_disconnect)
reactor.addSystemEventTrigger('before', 'shutdown', on_shutdown, f2)

if f3:
f3.on_connect.addCallback(on_connect)
f3.on_disconnect.addCallback(on_disconnect)
reactor.addSystemEventTrigger('before', 'shutdown', on_shutdown, f3)


# Block until proxy connect to the pool
try:
Expand All @@ -112,6 +139,7 @@ def main():
log.warning("First pool server must be online first time during start")
return


conn = reactor.listenTCP(settings.PORT, Site(getwork_listener.Root(job_registry, settings.ENABLE_WORKER_ID)), interface=settings.HOST)

try:
Expand Down
8 changes: 6 additions & 2 deletions mining_libs/client_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ def on_timeout(cls):
cls.reset_timeout()
if not cls.job_registry.f.is_connected:
cls.job_registry.f.reconnect()
if cls.job_registry.ff and not cls.job_registry.ff.is_connected:
cls.job_registry.ff.reconnect()
if cls.job_registry.f1 and not cls.job_registry.f1.is_connected:
cls.job_registry.f1.reconnect()
if cls.job_registry.f2 and not cls.job_registry.f2.is_connected:
cls.job_registry.f2.reconnect()
if cls.job_registry.f3 and not cls.job_registry.f3.is_connected:
cls.job_registry.f3.reconnect()

def handle_event(self, method, params, connection_ref):
'''Handle RPC calls and notifications from the pool'''
Expand Down
43 changes: 33 additions & 10 deletions mining_libs/getwork_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@

class Root(Resource):
isLeaf = True

def __init__(self, job_registry, enable_worker_id):
Resource.__init__(self)
self.job_registry = job_registry
self.isWorkerID = enable_worker_id
self.submitHashrates = {}

self.getWorkCacheTimeout = {"work":"","time":0}

def json_response(self, msg_id, result):
resp = json.dumps({'id': msg_id, 'jsonrpc': '2.0', 'result': result})
return resp

def json_error(self, msg_id, message):
resp = json.dumps({'id': msg_id, 'jsonrpc': '2.0', 'result': False, 'error': message})
return resp
def render_POST(self, request):
return resp

def render_POST(self, request):
request.setHeader('content-type', 'application/json')
data = json.loads(request.content.read())

Expand All @@ -37,11 +38,17 @@ def render_POST(self, request):

if not data.has_key('method'):
response = self.json_error(data.get('id'), "Need methods")+'\n'
if data['method'] == 'eth_getWork':
response = self.json_response(data.get('id', 0), self.job_registry.jobs.params)
elif data['method'] == 'eth_getWork':
if self.getWorkCacheTimeout["work"]==self.job_registry.jobs.params[0] and int(time.time())-self.getWorkCacheTimeout["time"]>=self.job_registry.coinTimeout:
log.warning('Job timeout. Proxy is waiting for an updated job. Please restart proxy!')
response = self.json_error(data.get('id', 0), "Job timeout. Proxy is waiting for an updated job...")
else:
if self.getWorkCacheTimeout["work"]!=self.job_registry.jobs.params[0]:
self.getWorkCacheTimeout = {"work":self.job_registry.jobs.params[0],"time":int(time.time())}
response = self.json_response(data.get('id', 0), self.job_registry.jobs.params)
elif data['method'] == 'eth_submitWork' or data['method'] == 'eth_submitHashrate':
if self.isWorkerID:
worker_name = request.uri[1:15]
worker_name = request.uri[1:15].split("/")[0]
if not worker_name:
ip_temp = request.getClientIP().split('.')
worker_name = str( int(ip_temp[0])*16777216 + int(ip_temp[1])*65536 + int(ip_temp[2])*256 + int(ip_temp[3]) )
Expand All @@ -51,6 +58,7 @@ def render_POST(self, request):
if data['method'] == 'eth_submitHashrate':
if worker_name and (not self.submitHashrates.has_key(worker_name) or int(time.time())-self.submitHashrates[worker_name]>=60):
self.submitHashrates[worker_name] = int(time.time())
log.info('Hashrate for %s is %s MHs' % (worker_name,int(data['params'][0],16)/1000000.0 ) )
threads.deferToThread(self.job_registry.submit, data['method'], data['params'], worker_name)
elif data['method'] == 'eth_submitWork':
threads.deferToThread(self.job_registry.submit, data['method'], data['params'], worker_name)
Expand All @@ -66,4 +74,19 @@ def render_POST(self, request):
return

def render_GET(self, request):
return "Ethereum startum proxy"
ret_text = "Ethereum stratum proxy<br>"
if self.job_registry and self.job_registry.jobs and self.job_registry.jobs.params:
ret_text += "DAG-file: %s<br><br>" % str(self.job_registry.jobs.params[1][2:18])
if self.job_registry.f:
connected = "connected" if (hasattr(self.job_registry.f, "is_connected") and self.job_registry.f.is_connected) else "disconnected"
ret_text += "Main server %s:%s (%s) %s<br>" % (self.job_registry.f.main_host[0], self.job_registry.f.main_host[1], self.job_registry.f.remote_ip, connected)
if self.job_registry.f1:
connected = "connected" if (hasattr(self.job_registry.f1, "is_connected") and self.job_registry.f1.is_connected) else "disconnected"
ret_text += "Failover server1 %s:%s (%s) %s<br>" % (self.job_registry.f1.main_host[0], self.job_registry.f1.main_host[1], self.job_registry.f1.remote_ip, connected)
if self.job_registry.f2:
connected = "connected" if (hasattr(self.job_registry.f2, "is_connected") and self.job_registry.f2.is_connected) else "disconnected"
ret_text += "Failover server2 %s:%s (%s) %s<br>" % (self.job_registry.f2.main_host[0], self.job_registry.f2.main_host[1], self.job_registry.f2.remote_ip, connected)
if self.job_registry.f3:
connected = "connected" if (hasattr(self.job_registry.f3, "is_connected") and self.job_registry.f3.is_connected) else "disconnected"
ret_text += "Failover server3 %s:%s (%s) %s<br>" % (self.job_registry.f3.main_host[0], self.job_registry.f3.main_host[1], self.job_registry.f3.remote_ip, connected)
return ret_text
Loading

0 comments on commit d993fc8

Please sign in to comment.