commit
6d523a42fa
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -7,3 +7,6 @@
|
||||
[submodule "externals/stratum"]
|
||||
path = externals/stratum
|
||||
url = https://github.com/slush0/stratum.git
|
||||
[submodule "externals/quarkcoin-hash"]
|
||||
path = externals/quarkcoin-hash
|
||||
url = https://github.com/Neisklar/quarkcoin-hash-python
|
||||
|
||||
@ -17,11 +17,16 @@ COINDAEMON_TRUSTED_USER = 'user'
|
||||
COINDAEMON_TRUSTED_PASSWORD = 'somepassword'
|
||||
|
||||
# Coin Algorithm is the option used to determine the algortithm used by stratum
|
||||
# This currently only works with POW SHA256 and Scrypt Coins
|
||||
# The available options are scrypt and sha256d.
|
||||
# This currently works with POW and POS coins
|
||||
# The available options are:
|
||||
# scrypt, sha256d, scrypt-jane and quark
|
||||
# If the option does not meet either of these criteria stratum defaults to scrypt
|
||||
# Until AutoReward Selecting Code has been implemented the below options are used to select the type of coin
|
||||
# For Reward type there is POW and POS. please ensure you choose the currect type.
|
||||
# For SHA256 PoS Coins which support TX Messages please enter yes in the TX selection
|
||||
COINDAEMON_ALGO = 'scrypt'
|
||||
|
||||
COINDAEMON_Reward = 'POW'
|
||||
COINDAEMON_SHA256_TX = 'no'
|
||||
# ******************** BASIC SETTINGS ***************
|
||||
# Backup Coin Daemon address's (consider having at least 1 backup)
|
||||
# You can have up to 99
|
||||
@ -83,12 +88,23 @@ PASSWORD_SALT = 'some_crazy_string'
|
||||
|
||||
# ******************** Database *********************
|
||||
|
||||
DATABASE_DRIVER = 'sqlite' # Options: none, sqlite, postgresql or mysql
|
||||
|
||||
# SQLite
|
||||
DB_SQLITE_FILE = 'pooldb.sqlite'
|
||||
# Postgresql
|
||||
DB_PGSQL_HOST = 'localhost'
|
||||
DB_PGSQL_DBNAME = 'pooldb'
|
||||
DB_PGSQL_USER = 'pooldb'
|
||||
DB_PGSQL_PASS = '**empty**'
|
||||
DB_PGSQL_SCHEMA = 'public'
|
||||
# MySQL
|
||||
DB_MYSQL_HOST = 'localhost'
|
||||
DB_MYSQL_DBNAME = 'pooldb'
|
||||
DB_MYSQL_USER = 'pooldb'
|
||||
DB_MYSQL_PASS = '**empty**'
|
||||
|
||||
|
||||
# ******************** Adv. DB Settings *********************
|
||||
# Don't change these unless you know what you are doing
|
||||
|
||||
@ -124,25 +140,32 @@ MERKLE_REFRESH_INTERVAL = 60 # How often check memorypool
|
||||
INSTANCE_ID = 31 # Used for extranonce and needs to be 0-31
|
||||
|
||||
# ******************** Pool Difficulty Settings *********************
|
||||
# Again, Don't change unless you know what this is for.
|
||||
VDIFF_X2_TYPE = True # powers of 2 e.g. 2,4,8,16,32,64,128,256,512,1024
|
||||
VDIFF_FLOAT = False # Use float difficulty
|
||||
|
||||
# Pool Target (Base Difficulty)
|
||||
# In order to match the Pool Target with a frontend like MPOS the following formula is used: (stratum diff) ~= 2^((target bits in pushpool) - 16)
|
||||
# E.G. a Pool Target of 16 would = a MPOS and PushPool Target bit's of 20
|
||||
POOL_TARGET = 16 # Pool-wide difficulty target int >= 1
|
||||
POOL_TARGET = 32 # Pool-wide difficulty target int >= 1
|
||||
|
||||
# Variable Difficulty Enable
|
||||
VARIABLE_DIFF = True # Master variable difficulty enable
|
||||
VARIABLE_DIFF = True # Master variable difficulty enable
|
||||
|
||||
# Variable diff tuning variables
|
||||
#VARDIFF will start at the POOL_TARGET. It can go as low as the VDIFF_MIN and as high as min(VDIFF_MAX or the coin daemon's difficulty)
|
||||
USE_COINDAEMON_DIFF = False # Set the maximum difficulty to the coin daemon's difficulty.
|
||||
DIFF_UPDATE_FREQUENCY = 86400 # Update the COINDAEMON difficulty once a day for the VARDIFF maximum
|
||||
VDIFF_MIN_TARGET = 15 # Minimum Target difficulty
|
||||
VDIFF_MAX_TARGET = 1000 # Maximum Target difficulty
|
||||
VDIFF_TARGET_TIME = 30 # Target time per share (i.e. try to get 1 share per this many seconds)
|
||||
VDIFF_RETARGET_TIME = 120 # Check to see if we should retarget this often
|
||||
VDIFF_VARIANCE_PERCENT = 20 # Allow average time to very this % from target without retarget
|
||||
#VARDIFF will start at the POOL_TARGET. It can go as low as the VDIFF_MIN and as high as min(VDIFF_MAX or Liteconin's difficulty)
|
||||
USE_LITECOIN_DIFF = False # Set the maximum difficulty to the litecoin difficulty.
|
||||
DIFF_UPDATE_FREQUENCY = 86400 # Update the litecoin difficulty once a day for the VARDIFF maximum
|
||||
VDIFF_MIN_TARGET = 16 # Minimum Target difficulty
|
||||
VDIFF_MAX_TARGET = 1024 # Maximum Target difficulty
|
||||
VDIFF_TARGET_TIME = 15 # Target time per share (i.e. try to get 1 share per this many seconds)
|
||||
VDIFF_RETARGET_TIME = 120 # Check to see if we should retarget this often
|
||||
VDIFF_VARIANCE_PERCENT = 30 # Allow average time to very this % from target without retarget
|
||||
VDIFF_RETARGET_DELAY = 25 # Wait this many seconds before applying new variable difficulty target
|
||||
VDIFF_RETARGET_REJECT_TIME = 60 # Wait this many seconds before rejecting old difficulty shares
|
||||
|
||||
# Allow external setting of worker difficulty, checks pool_worker table datarow[6] position for target difficulty
|
||||
# if present or else defaults to pool target, over rides all other difficulty settings, no checks are made
|
||||
#for min or max limits this sould be done by your front end software
|
||||
ALLOW_EXTERNAL_DIFFICULTY = False
|
||||
|
||||
#### Advanced Option #####
|
||||
# For backwards compatibility, we send the scrypt hash to the solutions column in the shares table
|
||||
# For block confirmation, we have an option to send the block hash in
|
||||
@ -158,3 +181,18 @@ GW_ENABLE = True # Enable the Proxy
|
||||
GW_PORT = 3333 # Getwork Proxy Port
|
||||
GW_DISABLE_MIDSTATE = False # Disable midstate's (Faster but breaks some clients)
|
||||
GW_SEND_REAL_TARGET = True # Propigate >1 difficulty to Clients (breaks some clients)
|
||||
|
||||
# ******************** Worker Ban Options *********************
|
||||
ENABLE_WORKER_BANNING = True # enable/disable temporary worker banning
|
||||
WORKER_CACHE_TIME = 600 # How long the worker stats cache is good before we check and refresh
|
||||
WORKER_BAN_TIME = 300 # How long we temporarily ban worker
|
||||
INVALID_SHARES_PERCENT = 50 # Allow average invalid shares vary this % before we ban
|
||||
|
||||
# ******************** E-Mail Notification Settings *********************
|
||||
NOTIFY_EMAIL_TO = '' # Where to send Start/Found block notifications
|
||||
NOTIFY_EMAIL_TO_DEADMINER = '' # Where to send dead miner notifications
|
||||
NOTIFY_EMAIL_FROM = 'root@localhost' # Sender address
|
||||
NOTIFY_EMAIL_SERVER = 'localhost' # E-Mail Sender
|
||||
NOTIFY_EMAIL_USERNAME = '' # E-Mail server SMTP Logon
|
||||
NOTIFY_EMAIL_PASSWORD = ''
|
||||
NOTIFY_EMAIL_USETLS = True
|
||||
|
||||
1
externals/quarkcoin-hash
vendored
Submodule
1
externals/quarkcoin-hash
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8855c585d06f510134a8c66b686705f494bda0d9
|
||||
@ -124,7 +124,6 @@ class BitcoinRPCManager(object):
|
||||
except:
|
||||
self.next_connection()
|
||||
|
||||
|
||||
def getdifficulty(self):
|
||||
while True:
|
||||
try:
|
||||
|
||||
@ -19,6 +19,7 @@ class BlockTemplate(halfnode.CBlock):
|
||||
coinbase_transaction_class = CoinbaseTransaction
|
||||
|
||||
def __init__(self, timestamper, coinbaser, job_id):
|
||||
print("Hit Block_template.py")
|
||||
super(BlockTemplate, self).__init__()
|
||||
|
||||
self.job_id = job_id
|
||||
@ -46,10 +47,13 @@ class BlockTemplate(halfnode.CBlock):
|
||||
#txhashes = [None] + [ binascii.unhexlify(t['hash']) for t in data['transactions'] ]
|
||||
txhashes = [None] + [ util.ser_uint256(int(t['hash'], 16)) for t in data['transactions'] ]
|
||||
mt = merkletree.MerkleTree(txhashes)
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
coinbase = self.coinbase_transaction_class(self.timestamper, self.coinbaser, data['coinbasevalue'], data['coinbaseaux']['flags'], data['height'],
|
||||
settings.COINBASE_EXTRAS)
|
||||
else:
|
||||
coinbase = self.coinbase_transaction_class(self.timestamper, self.coinbaser, data['coinbasevalue'], data['coinbaseaux']['flags'], data['height'],
|
||||
settings.COINBASE_EXTRAS, data['curtime'])
|
||||
|
||||
coinbase = self.coinbase_transaction_class(self.timestamper, self.coinbaser, data['coinbasevalue'],
|
||||
data['coinbaseaux']['flags'], data['height'], settings.COINBASE_EXTRAS)
|
||||
|
||||
self.height = data['height']
|
||||
self.nVersion = data['version']
|
||||
self.hashPrevBlock = int(data['previousblockhash'], 16)
|
||||
|
||||
@ -9,59 +9,71 @@ log = lib.logger.get_logger('coinbaser')
|
||||
# TODO: Add on_* hooks in the app
|
||||
|
||||
class SimpleCoinbaser(object):
|
||||
'''This very simple coinbaser uses a constant coin address
|
||||
'''This very simple coinbaser uses constant bitcoin address
|
||||
for all generated blocks.'''
|
||||
|
||||
def __init__(self, bitcoin_rpc, address):
|
||||
# Fire callback when coinbaser is ready
|
||||
self.on_load = defer.Deferred()
|
||||
|
||||
print("hit the coinbaser")
|
||||
# Fire Callback when the coinbaser is ready
|
||||
self.on_load = defer.Deferred()
|
||||
|
||||
self.address = address
|
||||
self.is_valid = False # We need to check if pool can use this address
|
||||
|
||||
self.bitcoin_rpc = bitcoin_rpc
|
||||
self._validate()
|
||||
self.is_valid = False
|
||||
|
||||
self.bitcoin_rpc = bitcoin_rpc
|
||||
self._validate()
|
||||
|
||||
def _validate(self):
|
||||
d = self.bitcoin_rpc.validateaddress(self.address)
|
||||
d.addCallback(self._address_check)
|
||||
d.addErrback(self._failure)
|
||||
|
||||
def _address_check(self, result):
|
||||
if result['isvalid'] and result['ismine']:
|
||||
self.is_valid = True
|
||||
log.info("Coinbase address '%s' is valid" % self.address)
|
||||
|
||||
if not self.on_load.called:
|
||||
self.on_load.callback(True)
|
||||
|
||||
elif result['isvalid'] and settings.ALLOW_NONLOCAL_WALLET == True :
|
||||
self.is_valid = True
|
||||
log.warning("!!! Coinbase address '%s' is valid BUT it is not local" % self.address)
|
||||
|
||||
if not self.on_load.called:
|
||||
self.on_load.callback(True)
|
||||
d = self.bitcoin_rpc.validateaddress(self.address)
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
d.addCallback(self._POW_address_check)
|
||||
else: d.addCallback(self._POS_address_check)
|
||||
d.addErrback(self._failure)
|
||||
|
||||
def _POW_address_check(self, result):
|
||||
if result['isvalid'] and result['ismine']:
|
||||
self.is_valid = True
|
||||
log.info("Coinbase address '%s' is valid" % self.address)
|
||||
|
||||
if not self.on_load.called:
|
||||
self.on_load.callback(True)
|
||||
|
||||
elif result['isvalid'] and settings.ALLOW_NONLOCAL_WALLET == True :
|
||||
self.is_valid = True
|
||||
log.warning("!!! Coinbase address '%s' is valid BUT it is not local" % self.address)
|
||||
|
||||
if not self.on_load.called:
|
||||
self.on_load.callback(True)
|
||||
|
||||
else:
|
||||
self.is_valid = False
|
||||
log.error("Coinbase address '%s' is NOT valid!" % self.address)
|
||||
else:
|
||||
self.is_valid = False
|
||||
log.error("Coinbase address '%s' is NOT valid!" % self.address)
|
||||
|
||||
def _failure(self, failure):
|
||||
log.error("Cannot validate Bitcoin address '%s'" % self.address)
|
||||
raise
|
||||
def _POS_address_check(self, result):
|
||||
print(result)
|
||||
print(result['pubkey'])
|
||||
self.pubkey = result['pubkey']
|
||||
print("You're PUBKEY is : ", self.pubkey)
|
||||
# Fire callback when coinbaser is ready
|
||||
self.on_load.callback(True)
|
||||
|
||||
#def on_new_block(self):
|
||||
# pass
|
||||
|
||||
#def on_new_template(self):
|
||||
# pass
|
||||
def _failure(self, failure):
|
||||
log.error("Cannot validate Bitcoin address '%s'" % self.address)
|
||||
raise
|
||||
|
||||
def get_script_pubkey(self):
|
||||
if not self.is_valid:
|
||||
# Try again, maybe the coind was down?
|
||||
self._validate()
|
||||
raise Exception("Coinbase address is not validated!")
|
||||
return util.script_to_address(self.address)
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
if not self.is_valid:
|
||||
self._validate()
|
||||
raise Exception("Wallet Address is Wrong")
|
||||
return util.script_to_address(self.address)
|
||||
else:
|
||||
return util.script_to_pubkey(self.pubkey)
|
||||
|
||||
def get_coinbase_data(self):
|
||||
return ''
|
||||
|
||||
@ -2,8 +2,9 @@ import binascii
|
||||
import halfnode
|
||||
import struct
|
||||
import util
|
||||
|
||||
class CoinbaseTransaction(halfnode.CTransaction):
|
||||
import settings
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
class CoinbaseTransaction(halfnode.CTransaction):
|
||||
'''Construct special transaction used for coinbase tx.
|
||||
It also implements quick serialization using pre-cached
|
||||
scriptSig template.'''
|
||||
@ -46,4 +47,97 @@ class CoinbaseTransaction(halfnode.CTransaction):
|
||||
raise Exception("Incorrect extranonce size")
|
||||
|
||||
(part1, part2) = self.vin[0]._scriptSig_template
|
||||
self.vin[0].scriptSig = part1 + extranonce + part2
|
||||
self.vin[0].scriptSig = part1 + extranonce + part2
|
||||
elif settings.COINDAEMON_Reward == 'POS' and settings.COINDAEMON_SHA256_TX == 'yes':
|
||||
class CoinbaseTransaction(halfnode.CTransaction):
|
||||
'''Construct special transaction used for coinbase tx.
|
||||
It also implements quick serialization using pre-cached
|
||||
scriptSig template.'''
|
||||
|
||||
extranonce_type = '>Q'
|
||||
extranonce_placeholder = struct.pack(extranonce_type, int('f000000ff111111f', 16))
|
||||
extranonce_size = struct.calcsize(extranonce_type)
|
||||
|
||||
def __init__(self, timestamper, coinbaser, value, flags, height, data, ntime):
|
||||
super(CoinbaseTransaction, self).__init__()
|
||||
|
||||
#self.extranonce = 0
|
||||
|
||||
if len(self.extranonce_placeholder) != self.extranonce_size:
|
||||
raise Exception("Extranonce placeholder don't match expected length!")
|
||||
|
||||
tx_in = halfnode.CTxIn()
|
||||
tx_in.prevout.hash = 0L
|
||||
tx_in.prevout.n = 2**32-1
|
||||
tx_in._scriptSig_template = (
|
||||
util.ser_number(height) + binascii.unhexlify(flags) + util.ser_number(int(timestamper.time())) + \
|
||||
chr(self.extranonce_size),
|
||||
util.ser_string(coinbaser.get_coinbase_data() + data)
|
||||
)
|
||||
|
||||
tx_in.scriptSig = tx_in._scriptSig_template[0] + self.extranonce_placeholder + tx_in._scriptSig_template[1]
|
||||
|
||||
tx_out = halfnode.CTxOut()
|
||||
tx_out.nValue = value
|
||||
tx_out.scriptPubKey = coinbaser.get_script_pubkey()
|
||||
|
||||
self.nTime = ntime
|
||||
self.strTxComment = "Mined By AhmedBodi's CryptoExpert Pools"
|
||||
self.vin.append(tx_in)
|
||||
self.vout.append(tx_out)
|
||||
|
||||
# Two parts of serialized coinbase, just put part1 + extranonce + part2 to have final serialized tx
|
||||
self._serialized = super(CoinbaseTransaction, self).serialize().split(self.extranonce_placeholder)
|
||||
|
||||
def set_extranonce(self, extranonce):
|
||||
if len(extranonce) != self.extranonce_size:
|
||||
raise Exception("Incorrect extranonce size")
|
||||
|
||||
(part1, part2) = self.vin[0]._scriptSig_template
|
||||
self.vin[0].scriptSig = part1 + extranonce + part2
|
||||
else:
|
||||
class CoinbaseTransaction(halfnode.CTransaction):
|
||||
'''Construct special transaction used for coinbase tx.
|
||||
It also implements quick serialization using pre-cached
|
||||
scriptSig template.'''
|
||||
|
||||
extranonce_type = '>Q'
|
||||
extranonce_placeholder = struct.pack(extranonce_type, int('f000000ff111111f', 16))
|
||||
extranonce_size = struct.calcsize(extranonce_type)
|
||||
|
||||
def __init__(self, timestamper, coinbaser, value, flags, height, data, ntime):
|
||||
super(CoinbaseTransaction, self).__init__()
|
||||
|
||||
#self.extranonce = 0
|
||||
|
||||
if len(self.extranonce_placeholder) != self.extranonce_size:
|
||||
raise Exception("Extranonce placeholder don't match expected length!")
|
||||
|
||||
tx_in = halfnode.CTxIn()
|
||||
tx_in.prevout.hash = 0L
|
||||
tx_in.prevout.n = 2**32-1
|
||||
tx_in._scriptSig_template = (
|
||||
util.ser_number(height) + binascii.unhexlify(flags) + util.ser_number(int(timestamper.time())) + \
|
||||
chr(self.extranonce_size),
|
||||
util.ser_string(coinbaser.get_coinbase_data() + data)
|
||||
)
|
||||
|
||||
tx_in.scriptSig = tx_in._scriptSig_template[0] + self.extranonce_placeholder + tx_in._scriptSig_template[1]
|
||||
|
||||
tx_out = halfnode.CTxOut()
|
||||
tx_out.nValue = value
|
||||
tx_out.scriptPubKey = coinbaser.get_script_pubkey()
|
||||
|
||||
self.nTime = ntime
|
||||
self.vin.append(tx_in)
|
||||
self.vout.append(tx_out)
|
||||
|
||||
# Two parts of serialized coinbase, just put part1 + extranonce + part2 to have final serialized tx
|
||||
self._serialized = super(CoinbaseTransaction, self).serialize().split(self.extranonce_placeholder)
|
||||
|
||||
def set_extranonce(self, extranonce):
|
||||
if len(extranonce) != self.extranonce_size:
|
||||
raise Exception("Incorrect extranonce size")
|
||||
|
||||
(part1, part2) = self.vin[0]._scriptSig_template
|
||||
self.vin[0].scriptSig = part1 + extranonce + part2
|
||||
|
||||
@ -111,11 +111,17 @@ COINDAEMON_TRUSTED_PORT = 8332 # RPC port
|
||||
COINDAEMON_TRUSTED_USER = 'stratum'
|
||||
COINDAEMON_TRUSTED_PASSWORD = '***somepassword***'
|
||||
|
||||
|
||||
# Coin Algorithm is the option used to determine the algortithm used by stratum
|
||||
# This currently only works with POW SHA256 and Scrypt Coins
|
||||
# The available options are scrypt and sha256d.
|
||||
# If the option does not meet either of these criteria stratum defaults to scrypt
|
||||
# If the option does not meet either of these criteria stratum defaults to scry$
|
||||
# Until AutoReward Selecting Code has been implemented the below options are us$
|
||||
# For Reward type there is POW and POS. please ensure you choose the currect ty$
|
||||
# For SHA256 PoS Coins which support TX Messages please enter yes in the TX sel$
|
||||
COINDAEMON_ALGO = 'scrypt'
|
||||
COINDAEMON_Reward = 'POW'
|
||||
COINDAEMON_SHA256_TX = 'yes'
|
||||
|
||||
# ******************** OTHER CORE SETTINGS *********************
|
||||
# Use "echo -n '<yourpassword>' | sha256sum | cut -f1 -d' ' "
|
||||
|
||||
129
lib/halfnode.py
129
lib/halfnode.py
@ -19,10 +19,27 @@ import settings
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
print("########################################### Loading LTC Scrypt Module #########################################################")
|
||||
import ltc_scrypt
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
print("########################################### Loading Quark Module #########################################################")
|
||||
import quark_hash
|
||||
else:
|
||||
print("########################################### NOT Loading LTC Scrypt Module ######################################################")
|
||||
pass
|
||||
|
||||
if settings.COINDAEMON_Reward == 'POS':
|
||||
print("########################################### Loading POS Support #########################################################")
|
||||
pass
|
||||
else:
|
||||
print("########################################### NOT Loading POS Support ######################################################")
|
||||
pass
|
||||
|
||||
if settings.COINDAEMON_Reward == 'POS' and settings.COINDAEMON_SHA256_TX == 'yes':
|
||||
print("########################################### Loading SHA256 Transaction Message Support #########################################################")
|
||||
pass
|
||||
else:
|
||||
print("########################################### NOT Loading SHA256 Transaction Message Support ######################################################")
|
||||
pass
|
||||
|
||||
import lib.logger
|
||||
log = lib.logger.get_logger('halfnode')
|
||||
|
||||
@ -138,25 +155,74 @@ class CTxOut(object):
|
||||
|
||||
class CTransaction(object):
|
||||
def __init__(self):
|
||||
self.nVersion = 1
|
||||
self.vin = []
|
||||
self.vout = []
|
||||
self.nLockTime = 0
|
||||
self.sha256 = None
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
self.nVersion = 1
|
||||
self.vin = []
|
||||
self.vout = []
|
||||
self.nLockTime = 0
|
||||
self.sha256 = None
|
||||
elif settings.COINDAEMON_Reward == 'POS' and settings.COINDAEMON_SHA256_TX == 'yes':
|
||||
self.nVersion = 1
|
||||
self.nTime = 0
|
||||
self.vin = []
|
||||
self.vout = []
|
||||
self.nLockTime = 0
|
||||
self.sha256 = None
|
||||
self.strTxComment = ""
|
||||
else:
|
||||
self.nVersion = 1
|
||||
self.nTime = 0
|
||||
self.vin = []
|
||||
self.vout = []
|
||||
self.nLockTime = 0
|
||||
self.sha256 = None
|
||||
def deserialize(self, f):
|
||||
self.nVersion = struct.unpack("<i", f.read(4))[0]
|
||||
self.vin = deser_vector(f, CTxIn)
|
||||
self.vout = deser_vector(f, CTxOut)
|
||||
self.nLockTime = struct.unpack("<I", f.read(4))[0]
|
||||
self.sha256 = None
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
self.nVersion = struct.unpack("<i", f.read(4))[0]
|
||||
self.vin = deser_vector(f, CTxIn)
|
||||
self.vout = deser_vector(f, CTxOut)
|
||||
self.nLockTime = struct.unpack("<I", f.read(4))[0]
|
||||
self.sha256 = None
|
||||
elif settings.COINDAEMON_Reward == 'POS' and settings.COINDAEMON_SHA256_TX == 'yes':
|
||||
self.nVersion = struct.unpack("<i", f.read(4))[0]
|
||||
self.nTime = struct.unpack("<i", f.read(4))[0]
|
||||
self.vin = deser_vector(f, CTxIn)
|
||||
self.vout = deser_vector(f, CTxOut)
|
||||
self.nLockTime = struct.unpack("<I", f.read(4))[0]
|
||||
self.sha256 = None
|
||||
self.strTxComment = deser_string(f)
|
||||
else:
|
||||
self.nVersion = struct.unpack("<i", f.read(4))[0]
|
||||
self.nTime = struct.unpack("<i", f.read(4))[0]
|
||||
self.vin = deser_vector(f, CTxIn)
|
||||
self.vout = deser_vector(f, CTxOut)
|
||||
self.nLockTime = struct.unpack("<I", f.read(4))[0]
|
||||
self.sha256 = None
|
||||
def serialize(self):
|
||||
r = ""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
r += ser_vector(self.vin)
|
||||
r += ser_vector(self.vout)
|
||||
r += struct.pack("<I", self.nLockTime)
|
||||
return r
|
||||
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
r = ""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
r += ser_vector(self.vin)
|
||||
r += ser_vector(self.vout)
|
||||
r += struct.pack("<I", self.nLockTime)
|
||||
return r
|
||||
elif settings.COINDAEMON_Reward == 'POS' and settings.COINDAEMON_SHA256_TX == 'yes':
|
||||
r = ""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
r += struct.pack("<i", self.nTime)
|
||||
r += ser_vector(self.vin)
|
||||
r += ser_vector(self.vout)
|
||||
r += struct.pack("<I", self.nLockTime)
|
||||
r += ser_string(self.strTxComment)
|
||||
return r
|
||||
else:
|
||||
r = ""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
r += struct.pack("<i", self.nTime)
|
||||
r += ser_vector(self.vin)
|
||||
r += ser_vector(self.vout)
|
||||
r += struct.pack("<I", self.nLockTime)
|
||||
return r
|
||||
def calc_sha256(self):
|
||||
if self.sha256 is None:
|
||||
self.sha256 = uint256_from_str(SHA256.new(SHA256.new(self.serialize()).digest()).digest())
|
||||
@ -183,7 +249,13 @@ class CBlock(object):
|
||||
self.sha256 = None
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
self.scrypt = None
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
self.quark = None
|
||||
else: pass
|
||||
if settings.COINDAEMON_Reward == 'POS':
|
||||
self.signature = b""
|
||||
else: pass
|
||||
|
||||
def deserialize(self, f):
|
||||
self.nVersion = struct.unpack("<i", f.read(4))[0]
|
||||
self.hashPrevBlock = deser_uint256(f)
|
||||
@ -192,6 +264,9 @@ class CBlock(object):
|
||||
self.nBits = struct.unpack("<I", f.read(4))[0]
|
||||
self.nNonce = struct.unpack("<I", f.read(4))[0]
|
||||
self.vtx = deser_vector(f, CTransaction)
|
||||
if settings.COINDAEMON_Reward == 'POS':
|
||||
self.signature = deser_string(f)
|
||||
else: pass
|
||||
def serialize(self):
|
||||
r = []
|
||||
r.append(struct.pack("<i", self.nVersion))
|
||||
@ -201,6 +276,9 @@ class CBlock(object):
|
||||
r.append(struct.pack("<I", self.nBits))
|
||||
r.append(struct.pack("<I", self.nNonce))
|
||||
r.append(ser_vector(self.vtx))
|
||||
if settings.COINDAEMON_Reward == 'POS':
|
||||
r.append(ser_string(self.signature))
|
||||
else: pass
|
||||
return ''.join(r)
|
||||
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
@ -215,6 +293,18 @@ class CBlock(object):
|
||||
r.append(struct.pack("<I", self.nNonce))
|
||||
self.scrypt = uint256_from_str(ltc_scrypt.getPoWHash(''.join(r)))
|
||||
return self.scrypt
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
def calc_quark(self):
|
||||
if self.quark is None:
|
||||
r = []
|
||||
r.append(struct.pack("<i", self.nVersion))
|
||||
r.append(ser_uint256(self.hashPrevBlock))
|
||||
r.append(ser_uint256(self.hashMerkleRoot))
|
||||
r.append(struct.pack("<I", self.nTime))
|
||||
r.append(struct.pack("<I", self.nBits))
|
||||
r.append(struct.pack("<I", self.nNonce))
|
||||
self.quark = uint256_from_str(quark_hash.getPoWHash(''.join(r)))
|
||||
return self.quark
|
||||
else:
|
||||
def calc_sha256(self):
|
||||
if self.sha256 is None:
|
||||
@ -232,12 +322,17 @@ class CBlock(object):
|
||||
def is_valid(self):
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
self.calc_scrypt()
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
self.calc_quark()
|
||||
else:
|
||||
self.calc_sha256()
|
||||
target = uint256_from_compact(self.nBits)
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
if self.scrypt > target:
|
||||
return false
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
if self.quark > target:
|
||||
return false
|
||||
else:
|
||||
if self.sha256 > target:
|
||||
return False
|
||||
|
||||
43
lib/notify_email.py
Normal file
43
lib/notify_email.py
Normal file
@ -0,0 +1,43 @@
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from stratum import settings
|
||||
|
||||
import stratum.logger
|
||||
log = stratum.logger.get_logger('Notify_Email')
|
||||
|
||||
class NOTIFY_EMAIL():
|
||||
|
||||
def notify_start(self):
|
||||
if settings.NOTIFY_EMAIL_TO != '':
|
||||
self.send_email(settings.NOTIFY_EMAIL_TO,'Stratum Server Started','Stratum server has started!')
|
||||
|
||||
def notify_found_block(self,worker_name):
|
||||
if settings.NOTIFY_EMAIL_TO != '':
|
||||
text = '%s on Stratum server found a block!' % worker_name
|
||||
self.send_email(settings.NOTIFY_EMAIL_TO,'Stratum Server Found Block',text)
|
||||
|
||||
def notify_dead_coindaemon(self,worker_name):
|
||||
if settings.NOTIFY_EMAIL_TO != '':
|
||||
text = 'Coin Daemon Has Crashed Please Report' % worker_name
|
||||
self.send_email(settings.NOTIFY_EMAIL_TO,'Coin Daemon Crashed!',text)
|
||||
|
||||
def send_email(self,to,subject,message):
|
||||
msg = MIMEText(message)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = settings.NOTIFY_EMAIL_FROM
|
||||
msg['To'] = to
|
||||
try:
|
||||
s = smtplib.SMTP(settings.NOTIFY_EMAIL_SERVER)
|
||||
if settings.NOTIFY_EMAIL_USERNAME != '':
|
||||
if settings.NOTIFY_EMAIL_USETLS:
|
||||
s.ehlo()
|
||||
s.starttls()
|
||||
s.ehlo()
|
||||
s.login(settings.NOTIFY_EMAIL_USERNAME, settings.NOTIFY_EMAIL_PASSWORD)
|
||||
s.sendmail(settings.NOTIFY_EMAIL_FROM,to,msg.as_string())
|
||||
s.quit()
|
||||
except smtplib.SMTPAuthenticationError as e:
|
||||
log.error('Error sending Email: %s' % e[1])
|
||||
except Exception as e:
|
||||
log.error('Error sending Email: %s' % e[0])
|
||||
@ -5,6 +5,10 @@ import StringIO
|
||||
import settings
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
import ltc_scrypt
|
||||
elif settings.COINDAEMON_ALGO == 'scrypt-jane':
|
||||
import yac_scrypt
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
import quark_hash
|
||||
else: pass
|
||||
from twisted.internet import defer
|
||||
from lib.exceptions import SubmitException
|
||||
@ -131,7 +135,8 @@ class TemplateRegistry(object):
|
||||
start = Interfaces.timestamper.time()
|
||||
|
||||
template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id())
|
||||
template.fill_from_rpc(data)
|
||||
print("hit template registry")
|
||||
log.info(template.fill_from_rpc(data))
|
||||
self.add_template(template,data['height'])
|
||||
|
||||
log.info("Update finished, %.03f sec, %d txes" % \
|
||||
@ -144,6 +149,8 @@ class TemplateRegistry(object):
|
||||
'''Converts difficulty to target'''
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000
|
||||
else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000
|
||||
return diff1 / difficulty
|
||||
|
||||
@ -169,7 +176,7 @@ class TemplateRegistry(object):
|
||||
return j
|
||||
|
||||
def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce,
|
||||
difficulty):
|
||||
difficulty, submit_time):
|
||||
'''Check parameters and finalize block template. If it leads
|
||||
to valid block candidate, asynchronously submits the block
|
||||
back to the bitcoin network.
|
||||
@ -228,13 +235,19 @@ class TemplateRegistry(object):
|
||||
# 4. Reverse header and compare it with target of the user
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
hash_bin = ltc_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
else: hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
elif settings.COINDAEMON_ALGO == 'scrypt-jane':
|
||||
hash_bin = yac_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]), int(ntime, 16))
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
hash_bin = quark_hash.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
else: hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
hash_int = util.uint256_from_str(hash_bin)
|
||||
scrypt_hash_hex = "%064x" % hash_int
|
||||
header_hex = binascii.hexlify(header_bin)
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
if settings.COINDAEMON_ALGO == 'scrypt' or settings.COINDAEMON_ALGO == 'scrypt-jane':
|
||||
header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"
|
||||
else: pass
|
||||
elif settings.COINDAEMON_ALGO == 'quark':
|
||||
header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"
|
||||
else: pass
|
||||
|
||||
target_user = self.diff_to_target(difficulty)
|
||||
if hash_int > target_user and \
|
||||
@ -242,6 +255,10 @@ class TemplateRegistry(object):
|
||||
or 'prev_diff' not in session or hash_int > self.diff_to_target(session['prev_diff']) ):
|
||||
raise SubmitException("Share is above target")
|
||||
|
||||
if hash_int > target_user and 'prev_ts' in session \
|
||||
and (submit_time - session['prev_ts']) > settings.VDIFF_RETARGET_REJECT_TIME:
|
||||
raise SubmitException("Stale-share above target")
|
||||
|
||||
# Mostly for debugging purposes
|
||||
target_info = self.diff_to_target(100000)
|
||||
if hash_int <= target_info:
|
||||
|
||||
288
lib/template_registry.py.save
Normal file
288
lib/template_registry.py.save
Normal file
@ -0,0 +1,288 @@
|
||||
import weakref
|
||||
import binascii
|
||||
import util
|
||||
import StringIO
|
||||
import settings
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
import ltc_scrypt
|
||||
else: pass
|
||||
from twisted.internet import defer
|
||||
from lib.exceptions import SubmitException
|
||||
|
||||
import lib.logger
|
||||
log = lib.logger.get_logger('template_registry')
|
||||
|
||||
from mining.interfaces import Interfaces
|
||||
from extranonce_counter import ExtranonceCounter
|
||||
import lib.settings as settings
|
||||
|
||||
|
||||
class JobIdGenerator(object):
|
||||
'''Generate pseudo-unique job_id. It does not need to be absolutely unique,
|
||||
because pool sends "clean_jobs" flag to clients and they should drop all previous jobs.'''
|
||||
counter = 0
|
||||
|
||||
@classmethod
|
||||
def get_new_id(cls):
|
||||
cls.counter += 1
|
||||
if cls.counter % 0xffff == 0:
|
||||
cls.counter = 1
|
||||
return "%x" % cls.counter
|
||||
|
||||
class TemplateRegistry(object):
|
||||
'''Implements the main logic of the pool. Keep track
|
||||
on valid block templates, provide internal interface for stratum
|
||||
service and implements block validation and submits.'''
|
||||
|
||||
def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id,
|
||||
on_template_callback, on_block_callback):
|
||||
self.prevhashes = {}
|
||||
self.jobs = weakref.WeakValueDictionary()
|
||||
|
||||
self.extranonce_counter = ExtranonceCounter(instance_id)
|
||||
self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \
|
||||
- self.extranonce_counter.get_size()
|
||||
|
||||
self.coinbaser = coinbaser
|
||||
self.block_template_class = block_template_class
|
||||
self.bitcoin_rpc = bitcoin_rpc
|
||||
self.on_block_callback = on_block_callback
|
||||
self.on_template_callback = on_template_callback
|
||||
|
||||
self.last_block = None
|
||||
self.update_in_progress = False
|
||||
self.last_update = None
|
||||
|
||||
# Create first block template on startup
|
||||
self.update_block()
|
||||
|
||||
def get_new_extranonce1(self):
|
||||
'''Generates unique extranonce1 (e.g. for newly
|
||||
subscribed connection.'''
|
||||
return self.extranonce_counter.get_new_bin()
|
||||
|
||||
def get_last_broadcast_args(self):
|
||||
'''Returns arguments for mining.notify
|
||||
from last known template.'''
|
||||
return self.last_block.broadcast_args
|
||||
|
||||
def add_template(self, block,block_height):
|
||||
'''Adds new template to the registry.
|
||||
It also clean up templates which should
|
||||
not be used anymore.'''
|
||||
|
||||
prevhash = block.prevhash_hex
|
||||
|
||||
if prevhash in self.prevhashes.keys():
|
||||
new_block = False
|
||||
else:
|
||||
new_block = True
|
||||
self.prevhashes[prevhash] = []
|
||||
|
||||
# Blocks sorted by prevhash, so it's easy to drop
|
||||
# them on blockchain update
|
||||
self.prevhashes[prevhash].append(block)
|
||||
|
||||
# Weak reference for fast lookup using job_id
|
||||
self.jobs[block.job_id] = block
|
||||
|
||||
# Use this template for every new request
|
||||
self.last_block = block
|
||||
|
||||
# Drop templates of obsolete blocks
|
||||
for ph in self.prevhashes.keys():
|
||||
if ph != prevhash:
|
||||
del self.prevhashes[ph]
|
||||
|
||||
log.info("New template for %s" % prevhash)
|
||||
|
||||
if new_block:
|
||||
# Tell the system about new block
|
||||
# It is mostly important for share manager
|
||||
self.on_block_callback(prevhash, block_height)
|
||||
|
||||
# Everything is ready, let's broadcast jobs!
|
||||
self.on_template_callback(new_block)
|
||||
|
||||
|
||||
#from twisted.internet import reactor
|
||||
#reactor.callLater(10, self.on_block_callback, new_block)
|
||||
|
||||
def update_block(self):
|
||||
'''Registry calls the getblocktemplate() RPC
|
||||
and build new block template.'''
|
||||
|
||||
if self.update_in_progress:
|
||||
# Block has been already detected
|
||||
return
|
||||
|
||||
self.update_in_progress = True
|
||||
self.last_update = Interfaces.timestamper.time()
|
||||
|
||||
d = self.bitcoin_rpc.getblocktemplate()
|
||||
d.addCallback(self._update_block)
|
||||
d.addErrback(self._update_block_failed)
|
||||
|
||||
def _update_block_failed(self, failure):
|
||||
log.error(str(failure))
|
||||
self.update_in_progress = False
|
||||
|
||||
def _update_block(self, data):
|
||||
start = Interfaces.timestamper.time()
|
||||
|
||||
template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id())
|
||||
print("hit template registry")
|
||||
template.fill_from_rpc(data) template.fill_from_rpc(data)
|
||||
self.add_template(template,data['height'])
|
||||
|
||||
log.info("Update finished, %.03f sec, %d txes" % \
|
||||
(Interfaces.timestamper.time() - start, len(template.vtx)))
|
||||
|
||||
self.update_in_progress = False
|
||||
return data
|
||||
|
||||
def diff_to_target(self, difficulty):
|
||||
'''Converts difficulty to target'''
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000
|
||||
else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000
|
||||
return diff1 / difficulty
|
||||
|
||||
def get_job(self, job_id):
|
||||
'''For given job_id returns BlockTemplate instance or None'''
|
||||
try:
|
||||
j = self.jobs[job_id]
|
||||
except:
|
||||
log.info("Job id '%s' not found" % job_id)
|
||||
return None
|
||||
|
||||
# Now we have to check if job is still valid.
|
||||
# Unfortunately weak references are not bulletproof and
|
||||
# old reference can be found until next run of garbage collector.
|
||||
if j.prevhash_hex not in self.prevhashes:
|
||||
log.info("Prevhash of job '%s' is unknown" % job_id)
|
||||
return None
|
||||
|
||||
if j not in self.prevhashes[j.prevhash_hex]:
|
||||
log.info("Job %s is unknown" % job_id)
|
||||
return None
|
||||
|
||||
return j
|
||||
|
||||
def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce,
|
||||
difficulty):
|
||||
'''Check parameters and finalize block template. If it leads
|
||||
to valid block candidate, asynchronously submits the block
|
||||
back to the bitcoin network.
|
||||
|
||||
- extranonce1_bin is binary. No checks performed, it should be from session data
|
||||
- job_id, extranonce2, ntime, nonce - in hex form sent by the client
|
||||
- difficulty - decimal number from session, again no checks performed
|
||||
- submitblock_callback - reference to method which receive result of submitblock()
|
||||
'''
|
||||
|
||||
# Check if extranonce2 looks correctly. extranonce2 is in hex form...
|
||||
if len(extranonce2) != self.extranonce2_size * 2:
|
||||
raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2))
|
||||
|
||||
# Check for job
|
||||
job = self.get_job(job_id)
|
||||
if job == None:
|
||||
raise SubmitException("Job '%s' not found" % job_id)
|
||||
|
||||
# Check if ntime looks correct
|
||||
if len(ntime) != 8:
|
||||
raise SubmitException("Incorrect size of ntime. Expected 8 chars")
|
||||
|
||||
if not job.check_ntime(int(ntime, 16)):
|
||||
raise SubmitException("Ntime out of range")
|
||||
|
||||
# Check nonce
|
||||
if len(nonce) != 8:
|
||||
raise SubmitException("Incorrect size of nonce. Expected 8 chars")
|
||||
|
||||
# Check for duplicated submit
|
||||
if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce):
|
||||
log.info("Duplicate from %s, (%s %s %s %s)" % \
|
||||
(worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce))
|
||||
raise SubmitException("Duplicate share")
|
||||
|
||||
# Now let's do the hard work!
|
||||
# ---------------------------
|
||||
|
||||
# 0. Some sugar
|
||||
extranonce2_bin = binascii.unhexlify(extranonce2)
|
||||
ntime_bin = binascii.unhexlify(ntime)
|
||||
nonce_bin = binascii.unhexlify(nonce)
|
||||
|
||||
# 1. Build coinbase
|
||||
coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin)
|
||||
coinbase_hash = util.doublesha(coinbase_bin)
|
||||
|
||||
# 2. Calculate merkle root
|
||||
merkle_root_bin = job.merkletree.withFirst(coinbase_hash)
|
||||
merkle_root_int = util.uint256_from_str(merkle_root_bin)
|
||||
|
||||
# 3. Serialize header with given merkle, ntime and nonce
|
||||
header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin)
|
||||
|
||||
# 4. Reverse header and compare it with target of the user
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
hash_bin = ltc_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
else: hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
hash_int = util.uint256_from_str(hash_bin)
|
||||
scrypt_hash_hex = "%064x" % hash_int
|
||||
header_hex = binascii.hexlify(header_bin)
|
||||
if settings.COINDAEMON_ALGO == 'scrypt':
|
||||
header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"
|
||||
else: pass
|
||||
|
||||
target_user = self.diff_to_target(difficulty)
|
||||
if hash_int > target_user and \
|
||||
( 'prev_jobid' not in session or session['prev_jobid'] < job_id \
|
||||
or 'prev_diff' not in session or hash_int > self.diff_to_target(session['prev_diff']) ):
|
||||
raise SubmitException("Share is above target")
|
||||
|
||||
# Mostly for debugging purposes
|
||||
target_info = self.diff_to_target(100000)
|
||||
if hash_int <= target_info:
|
||||
log.info("Yay, share with diff above 100000")
|
||||
|
||||
# Algebra tells us the diff_to_target is the same as hash_to_diff
|
||||
share_diff = int(self.diff_to_target(hash_int))
|
||||
|
||||
|
||||
# 5. Compare hash with target of the network
|
||||
if hash_int <= job.target:
|
||||
# Yay! It is block candidate!
|
||||
log.info("We found a block candidate! %s" % scrypt_hash_hex)
|
||||
|
||||
# Reverse the header and get the potential block hash (for scrypt only)
|
||||
block_hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
block_hash_hex = block_hash_bin[::-1].encode('hex_codec')
|
||||
|
||||
# 6. Finalize and serialize block object
|
||||
job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16))
|
||||
|
||||
if not job.is_valid():
|
||||
# Should not happen
|
||||
log.error("Final job validation failed!")
|
||||
|
||||
# 7. Submit block to the network
|
||||
serialized = binascii.hexlify(job.serialize())
|
||||
on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash_hex)
|
||||
if on_submit:
|
||||
self.update_block()
|
||||
|
||||
if settings.SOLUTION_BLOCK_HASH:
|
||||
return (header_hex, block_hash_hex, share_diff, on_submit)
|
||||
else:
|
||||
return (header_hex, scrypt_hash_hex, share_diff, on_submit)
|
||||
|
||||
if settings.SOLUTION_BLOCK_HASH:
|
||||
# Reverse the header and get the potential block hash (for scrypt only) only do this if we want to send in the block hash to the shares table
|
||||
block_hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
|
||||
block_hash_hex = block_hash_bin[::-1].encode('hex_codec')
|
||||
return (header_hex, block_hash_hex, share_diff, None)
|
||||
else:
|
||||
return (header_hex, scrypt_hash_hex, share_diff, None)
|
||||
22
lib/util.py
22
lib/util.py
@ -3,6 +3,8 @@
|
||||
import struct
|
||||
import StringIO
|
||||
import binascii
|
||||
import settings
|
||||
import bitcoin_rpc
|
||||
from hashlib import sha256
|
||||
|
||||
def deser_string(f):
|
||||
@ -171,7 +173,7 @@ def address_to_pubkeyhash(addr):
|
||||
addr = b58decode(addr, 25)
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
if addr is None:
|
||||
return None
|
||||
|
||||
@ -209,9 +211,15 @@ def ser_number(n):
|
||||
s.append(n)
|
||||
return bytes(s)
|
||||
|
||||
def script_to_address(addr):
|
||||
d = address_to_pubkeyhash(addr)
|
||||
if not d:
|
||||
raise ValueError('invalid address')
|
||||
(ver, pubkeyhash) = d
|
||||
return b'\x76\xa9\x14' + pubkeyhash + b'\x88\xac'
|
||||
if settings.COINDAEMON_Reward == 'POW':
|
||||
def script_to_address(addr):
|
||||
d = address_to_pubkeyhash(addr)
|
||||
if not d:
|
||||
raise ValueError('invalid address')
|
||||
(ver, pubkeyhash) = d
|
||||
return b'\x76\xa9\x14' + pubkeyhash + b'\x88\xac'
|
||||
else:
|
||||
def script_to_pubkey(key):
|
||||
if len(key) == 66: key = binascii.unhexlify(key)
|
||||
if len(key) != 33: raise Exception('invalid pubkey passed to script_to_pubkey')
|
||||
return b'\x21' + key + b'\xac'
|
||||
|
||||
@ -39,14 +39,33 @@ class DBInterface():
|
||||
self.bitcoinrpc = bitcoinrpc
|
||||
|
||||
def connectDB(self):
|
||||
if settings.VARIABLE_DIFF:
|
||||
log.debug("DB_Mysql_Vardiff INIT")
|
||||
import DB_Mysql_Vardiff
|
||||
return DB_Mysql_Vardiff.DB_Mysql_Vardiff()
|
||||
else:
|
||||
log.debug('DB_Mysql INIT')
|
||||
import DB_Mysql
|
||||
return DB_Mysql.DB_Mysql()
|
||||
if settings.DATABASE_DRIVER == "sqlite":
|
||||
log.debug('DB_Sqlite INIT')
|
||||
import DB_Sqlite
|
||||
return DB_Sqlite.DB_Sqlite()
|
||||
elif settings.DATABASE_DRIVER == "mysql":
|
||||
if settings.VARIABLE_DIFF:
|
||||
log.debug("DB_Mysql_Vardiff INIT")
|
||||
import DB_Mysql_Vardiff
|
||||
return DB_Mysql_Vardiff.DB_Mysql_Vardiff()
|
||||
else:
|
||||
log.debug('DB_Mysql INIT')
|
||||
import DB_Mysql
|
||||
return DB_Mysql.DB_Mysql()
|
||||
elif settings.DATABASE_DRIVER == "postgresql":
|
||||
log.debug('DB_Postgresql INIT')
|
||||
import DB_Postgresql
|
||||
return DB_Postgresql.DB_Postgresql()
|
||||
elif settings.DATABASE_DRIVER == "none":
|
||||
log.debug('DB_None INIT')
|
||||
import DB_None
|
||||
return DB_None.DB_None()
|
||||
else:
|
||||
log.error('Invalid DATABASE_DRIVER -- using NONE')
|
||||
log.debug('DB_None INIT')
|
||||
import DB_None
|
||||
return DB_None.DB_None()
|
||||
|
||||
|
||||
def clearusercache(self):
|
||||
log.debug("DBInterface.clearusercache called")
|
||||
@ -55,7 +74,9 @@ class DBInterface():
|
||||
|
||||
def scheduleImport(self):
|
||||
# This schedule's the Import
|
||||
use_thread = True
|
||||
if settings.DATABASE_DRIVER == "sqlite":
|
||||
use_thread = False
|
||||
else: use_thread = True
|
||||
|
||||
if use_thread:
|
||||
self.queueclock = reactor.callLater(settings.DB_LOADER_CHECKTIME , self.run_import_thread)
|
||||
|
||||
54
mining/DB_None.py
Normal file
54
mining/DB_None.py
Normal file
@ -0,0 +1,54 @@
|
||||
import stratum.logger
|
||||
log = stratum.logger.get_logger('None')
|
||||
|
||||
class DB_None():
|
||||
def __init__(self):
|
||||
log.debug("Connecting to DB")
|
||||
|
||||
def updateStats(self,averageOverTime):
|
||||
log.debug("Updating Stats")
|
||||
|
||||
def import_shares(self,data):
|
||||
log.debug("Importing Shares")
|
||||
|
||||
def found_block(self,data):
|
||||
log.debug("Found Block")
|
||||
|
||||
def get_user(self, id_or_username):
|
||||
log.debug("Get User")
|
||||
|
||||
def list_users(self):
|
||||
log.debug("List Users")
|
||||
|
||||
def delete_user(self,username):
|
||||
log.debug("Deleting Username")
|
||||
|
||||
def insert_user(self,username,password):
|
||||
log.debug("Adding Username/Password")
|
||||
|
||||
def update_user(self,username,password):
|
||||
log.debug("Updating Username/Password")
|
||||
|
||||
def check_password(self,username,password):
|
||||
log.debug("Checking Username/Password")
|
||||
return True
|
||||
|
||||
def update_pool_info(self,pi):
|
||||
log.debug("Update Pool Info")
|
||||
|
||||
def get_pool_stats(self):
|
||||
log.debug("Get Pool Stats")
|
||||
ret = {}
|
||||
return ret
|
||||
|
||||
def get_workers_stats(self):
|
||||
log.debug("Get Workers Stats")
|
||||
ret = {}
|
||||
return ret
|
||||
|
||||
def check_tables(self):
|
||||
log.debug("Checking Tables")
|
||||
|
||||
def close(self):
|
||||
log.debug("Close Connection")
|
||||
|
||||
394
mining/DB_Postgresql.py
Normal file
394
mining/DB_Postgresql.py
Normal file
@ -0,0 +1,394 @@
|
||||
import time
|
||||
import hashlib
|
||||
from stratum import settings
|
||||
import stratum.logger
|
||||
log = stratum.logger.get_logger('DB_Postgresql')
|
||||
|
||||
import psycopg2
|
||||
from psycopg2 import extras
|
||||
|
||||
class DB_Postgresql():
|
||||
def __init__(self):
|
||||
log.debug("Connecting to DB")
|
||||
self.dbh = psycopg2.connect("host='"+settings.DB_PGSQL_HOST+"' dbname='"+settings.DB_PGSQL_DBNAME+"' user='"+settings.DB_PGSQL_USER+\
|
||||
"' password='"+settings.DB_PGSQL_PASS+"'")
|
||||
# TODO -- set the schema
|
||||
self.dbc = self.dbh.cursor()
|
||||
|
||||
if hasattr(settings, 'PASSWORD_SALT'):
|
||||
self.salt = settings.PASSWORD_SALT
|
||||
else:
|
||||
raise ValueError("PASSWORD_SALT isn't set, please set in config.py")
|
||||
|
||||
def updateStats(self,averageOverTime):
|
||||
log.debug("Updating Stats")
|
||||
# Note: we are using transactions... so we can set the speed = 0 and it doesn't take affect until we are commited.
|
||||
self.dbc.execute("update pool_worker set speed = 0, alive = 'f'");
|
||||
stime = '%.2f' % ( time.time() - averageOverTime );
|
||||
self.dbc.execute("select username,SUM(difficulty) from shares where time > to_timestamp(%s) group by username", [stime])
|
||||
total_speed = 0
|
||||
for name,shares in self.dbc.fetchall():
|
||||
speed = int(int(shares) * pow(2,32)) / ( int(averageOverTime) * 1000 * 1000)
|
||||
total_speed += speed
|
||||
self.dbc.execute("update pool_worker set speed = %s, alive = 't' where username = %s", (speed,name))
|
||||
self.dbc.execute("update pool set value = %s where parameter = 'pool_speed'",[total_speed])
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_check(self):
|
||||
# Check for found shares to archive
|
||||
self.dbc.execute("select time from shares where upstream_result = true order by time limit 1")
|
||||
data = self.dbc.fetchone()
|
||||
if data is None or (data[0] + settings.ARCHIVE_DELAY) > time.time() :
|
||||
return False
|
||||
return data[0]
|
||||
|
||||
def archive_found(self,found_time):
|
||||
self.dbc.execute("insert into shares_archive_found select * from shares where upstream_result = true and time <= to_timestamp(%s)", [found_time])
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_to_db(self,found_time):
|
||||
self.dbc.execute("insert into shares_archive select * from shares where time <= to_timestamp(%s)",[found_time])
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_cleanup(self,found_time):
|
||||
self.dbc.execute("delete from shares where time <= to_timestamp(%s)",[found_time])
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_get_shares(self,found_time):
|
||||
self.dbc.execute("select * from shares where time <= to_timestamp(%s)",[found_time])
|
||||
return self.dbc
|
||||
|
||||
def import_shares(self,data):
|
||||
log.debug("Importing Shares")
|
||||
# 0 1 2 3 4 5 6 7 8 9 10
|
||||
# data: [worker_name,block_header,block_hash,difficulty,timestamp,is_valid,ip,block_height,prev_hash,invalid_reason,best_diff]
|
||||
checkin_times = {}
|
||||
total_shares = 0
|
||||
best_diff = 0
|
||||
for k,v in enumerate(data):
|
||||
if settings.DATABASE_EXTEND :
|
||||
total_shares += v[3]
|
||||
if v[0] in checkin_times:
|
||||
if v[4] > checkin_times[v[0]] :
|
||||
checkin_times[v[0]]["time"] = v[4]
|
||||
else:
|
||||
checkin_times[v[0]] = {"time": v[4], "shares": 0, "rejects": 0 }
|
||||
|
||||
if v[5] == True :
|
||||
checkin_times[v[0]]["shares"] += v[3]
|
||||
else :
|
||||
checkin_times[v[0]]["rejects"] += v[3]
|
||||
|
||||
if v[10] > best_diff:
|
||||
best_diff = v[10]
|
||||
|
||||
self.dbc.execute("insert into shares " +\
|
||||
"(time,rem_host,username,our_result,upstream_result,reason,solution,block_num,prev_block_hash,useragent,difficulty) " +\
|
||||
"VALUES (to_timestamp(%s),%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
|
||||
(v[4],v[6],v[0],bool(v[5]),False,v[9],'',v[7],v[8],'',v[3]) )
|
||||
else :
|
||||
self.dbc.execute("insert into shares (time,rem_host,username,our_result,upstream_result,reason,solution) VALUES " +\
|
||||
"(to_timestamp(%s),%s,%s,%s,%s,%s,%s)",
|
||||
(v[4],v[6],v[0],bool(v[5]),False,v[9],'') )
|
||||
|
||||
if settings.DATABASE_EXTEND :
|
||||
self.dbc.execute("select value from pool where parameter = 'round_shares'")
|
||||
round_shares = int(self.dbc.fetchone()[0]) + total_shares
|
||||
self.dbc.execute("update pool set value = %s where parameter = 'round_shares'",[round_shares])
|
||||
|
||||
self.dbc.execute("select value from pool where parameter = 'round_best_share'")
|
||||
round_best_share = int(self.dbc.fetchone()[0])
|
||||
if best_diff > round_best_share:
|
||||
self.dbc.execute("update pool set value = %s where parameter = 'round_best_share'",[best_diff])
|
||||
|
||||
self.dbc.execute("select value from pool where parameter = 'bitcoin_difficulty'")
|
||||
difficulty = float(self.dbc.fetchone()[0])
|
||||
|
||||
if difficulty == 0:
|
||||
progress = 0
|
||||
else:
|
||||
progress = (round_shares/difficulty)*100
|
||||
self.dbc.execute("update pool set value = %s where parameter = 'round_progress'",[progress])
|
||||
|
||||
for k,v in checkin_times.items():
|
||||
self.dbc.execute("update pool_worker set last_checkin = to_timestamp(%s), total_shares = total_shares + %s, total_rejects = total_rejects + %s where username = %s",
|
||||
(v["time"],v["shares"],v["rejects"],k))
|
||||
|
||||
self.dbh.commit()
|
||||
|
||||
|
||||
def found_block(self,data):
|
||||
# Note: difficulty = -1 here
|
||||
self.dbc.execute("update shares set upstream_result = %s, solution = %s where id in (select id from shares where time = to_timestamp(%s) and username = %s limit 1)",
|
||||
(bool(data[5]),data[2],data[4],data[0]))
|
||||
if settings.DATABASE_EXTEND and data[5] == True :
|
||||
self.dbc.execute("update pool_worker set total_found = total_found + 1 where username = %s",(data[0],))
|
||||
self.dbc.execute("select value from pool where parameter = 'pool_total_found'")
|
||||
total_found = int(self.dbc.fetchone()[0]) + 1
|
||||
self.dbc.executemany("update pool set value = %s where parameter = %s",[(0,'round_shares'),
|
||||
(0,'round_progress'),
|
||||
(0,'round_best_share'),
|
||||
(time.time(),'round_start'),
|
||||
(total_found,'pool_total_found')
|
||||
])
|
||||
self.dbh.commit()
|
||||
|
||||
def get_user(self, id_or_username):
|
||||
log.debug("Finding user with id or username of %s", id_or_username)
|
||||
cursor = self.dbh.cursor(cursor_factory=extras.DictCursor)
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT *
|
||||
FROM pool_worker
|
||||
WHERE id = %(id)s
|
||||
OR username = %(uname)s
|
||||
""",
|
||||
{
|
||||
"id": id_or_username if id_or_username.isdigit() else -1,
|
||||
"uname": id_or_username
|
||||
}
|
||||
)
|
||||
|
||||
user = cursor.fetchone()
|
||||
cursor.close()
|
||||
return user
|
||||
|
||||
def list_users(self):
|
||||
cursor = self.dbh.cursor(cursor_factory=extras.DictCursor)
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT *
|
||||
FROM pool_worker
|
||||
WHERE id > 0
|
||||
"""
|
||||
)
|
||||
|
||||
while True:
|
||||
results = cursor.fetchmany()
|
||||
if not results:
|
||||
break
|
||||
|
||||
for result in results:
|
||||
yield result
|
||||
|
||||
def delete_user(self, id_or_username):
|
||||
log.debug("Deleting Username")
|
||||
self.dbc.execute(
|
||||
"""
|
||||
delete from pool_worker where id = %(id)s or username = %(uname)s
|
||||
""",
|
||||
{
|
||||
"id": id_or_username if id_or_username.isdigit() else -1,
|
||||
"uname": id_or_username
|
||||
}
|
||||
)
|
||||
self.dbh.commit()
|
||||
|
||||
def insert_user(self,username,password):
|
||||
log.debug("Adding Username/Password")
|
||||
m = hashlib.sha1()
|
||||
m.update(password)
|
||||
m.update(self.salt)
|
||||
self.dbc.execute("insert into pool_worker (username,password) VALUES (%s,%s)",
|
||||
(username, m.hexdigest() ))
|
||||
self.dbh.commit()
|
||||
|
||||
return str(username)
|
||||
|
||||
def update_user(self, id_or_username, password):
|
||||
log.debug("Updating Username/Password")
|
||||
m = hashlib.sha1()
|
||||
m.update(password)
|
||||
m.update(self.salt)
|
||||
self.dbc.execute(
|
||||
"""
|
||||
update pool_worker set password = %(pass)s where id = %(id)s or username = %(uname)s
|
||||
""",
|
||||
{
|
||||
"id": id_or_username if id_or_username.isdigit() else -1,
|
||||
"uname": id_or_username,
|
||||
"pass": m.hexdigest()
|
||||
}
|
||||
)
|
||||
self.dbh.commit()
|
||||
|
||||
def update_worker_diff(self,username,diff):
|
||||
self.dbc.execute("update pool_worker set difficulty = %s where username = %s",(diff,username))
|
||||
self.dbh.commit()
|
||||
|
||||
def clear_worker_diff(self):
|
||||
if settings.DATABASE_EXTEND == True :
|
||||
self.dbc.execute("update pool_worker set difficulty = 0")
|
||||
self.dbh.commit()
|
||||
|
||||
def check_password(self,username,password):
|
||||
log.debug("Checking Username/Password")
|
||||
m = hashlib.sha1()
|
||||
m.update(password)
|
||||
m.update(self.salt)
|
||||
self.dbc.execute("select COUNT(*) from pool_worker where username = %s and password = %s",
|
||||
(username, m.hexdigest() ))
|
||||
data = self.dbc.fetchone()
|
||||
if data[0] > 0 :
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_pool_info(self,pi):
|
||||
self.dbc.executemany("update pool set value = %s where parameter = %s",[(pi['blocks'],"bitcoin_blocks"),
|
||||
(pi['balance'],"bitcoin_balance"),
|
||||
(pi['connections'],"bitcoin_connections"),
|
||||
(pi['difficulty'],"bitcoin_difficulty"),
|
||||
(time.time(),"bitcoin_infotime")
|
||||
])
|
||||
self.dbh.commit()
|
||||
|
||||
def get_pool_stats(self):
|
||||
self.dbc.execute("select * from pool")
|
||||
ret = {}
|
||||
for data in self.dbc.fetchall():
|
||||
ret[data[0]] = data[1]
|
||||
return ret
|
||||
|
||||
def get_workers_stats(self):
|
||||
self.dbc.execute("select username,speed,last_checkin,total_shares,total_rejects,total_found,alive,difficulty from pool_worker")
|
||||
ret = {}
|
||||
for data in self.dbc.fetchall():
|
||||
ret[data[0]] = { "username" : data[0],
|
||||
"speed" : data[1],
|
||||
"last_checkin" : time.mktime(data[2].timetuple()),
|
||||
"total_shares" : data[3],
|
||||
"total_rejects" : data[4],
|
||||
"total_found" : data[5],
|
||||
"alive" : data[6],
|
||||
"difficulty" : data[7] }
|
||||
return ret
|
||||
|
||||
def close(self):
|
||||
self.dbh.close()
|
||||
|
||||
def check_tables(self):
|
||||
log.debug("Checking Tables")
|
||||
|
||||
shares_exist = False
|
||||
self.dbc.execute("select COUNT(*) from pg_catalog.pg_tables where schemaname = %(schema)s and tablename = 'shares'",
|
||||
{"schema": settings.DB_PGSQL_SCHEMA })
|
||||
data = self.dbc.fetchone()
|
||||
if data[0] <= 0 :
|
||||
self.update_version_1()
|
||||
|
||||
if settings.DATABASE_EXTEND == True :
|
||||
self.update_tables()
|
||||
|
||||
|
||||
def update_tables(self):
|
||||
version = 0
|
||||
current_version = 7
|
||||
while version < current_version :
|
||||
self.dbc.execute("select value from pool where parameter = 'DB Version'")
|
||||
data = self.dbc.fetchone()
|
||||
version = int(data[0])
|
||||
if version < current_version :
|
||||
log.info("Updating Database from %i to %i" % (version, version +1))
|
||||
getattr(self, 'update_version_' + str(version) )()
|
||||
|
||||
def update_version_1(self):
|
||||
if settings.DATABASE_EXTEND == True :
|
||||
self.dbc.execute("create table shares" +\
|
||||
"(id serial primary key,time timestamp,rem_host TEXT, username TEXT, our_result BOOLEAN, upstream_result BOOLEAN, reason TEXT, solution TEXT, " +\
|
||||
"block_num INTEGER, prev_block_hash TEXT, useragent TEXT, difficulty INTEGER)")
|
||||
self.dbc.execute("create index shares_username ON shares(username)")
|
||||
self.dbc.execute("create table pool_worker" +\
|
||||
"(id serial primary key,username TEXT, password TEXT, speed INTEGER, last_checkin timestamp)")
|
||||
self.dbc.execute("create index pool_worker_username ON pool_worker(username)")
|
||||
self.dbc.execute("alter table pool_worker add total_shares INTEGER default 0")
|
||||
self.dbc.execute("alter table pool_worker add total_rejects INTEGER default 0")
|
||||
self.dbc.execute("alter table pool_worker add total_found INTEGER default 0")
|
||||
self.dbc.execute("create table pool(parameter TEXT, value TEXT)")
|
||||
self.dbc.execute("insert into pool (parameter,value) VALUES ('DB Version',2)")
|
||||
else :
|
||||
self.dbc.execute("create table shares" + \
|
||||
"(id serial,time timestamp,rem_host TEXT, username TEXT, our_result BOOLEAN, upstream_result BOOLEAN, reason TEXT, solution TEXT)")
|
||||
self.dbc.execute("create index shares_username ON shares(username)")
|
||||
self.dbc.execute("create table pool_worker(id serial,username TEXT, password TEXT)")
|
||||
self.dbc.execute("create index pool_worker_username ON pool_worker(username)")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_2(self):
|
||||
log.info("running update 2")
|
||||
self.dbc.executemany("insert into pool (parameter,value) VALUES (%s,%s)",[('bitcoin_blocks',0),
|
||||
('bitcoin_balance',0),
|
||||
('bitcoin_connections',0),
|
||||
('bitcoin_difficulty',0),
|
||||
('pool_speed',0),
|
||||
('pool_total_found',0),
|
||||
('round_shares',0),
|
||||
('round_progress',0),
|
||||
('round_start',time.time())
|
||||
])
|
||||
self.dbc.execute("update pool set value = 3 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_3(self):
|
||||
log.info("running update 3")
|
||||
self.dbc.executemany("insert into pool (parameter,value) VALUES (%s,%s)",[
|
||||
('round_best_share',0),
|
||||
('bitcoin_infotime',0)
|
||||
])
|
||||
self.dbc.execute("alter table pool_worker add alive BOOLEAN")
|
||||
self.dbc.execute("update pool set value = 4 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_4(self):
|
||||
log.info("running update 4")
|
||||
self.dbc.execute("alter table pool_worker add difficulty INTEGER default 0")
|
||||
self.dbc.execute("create table shares_archive" +\
|
||||
"(id serial primary key,time timestamp,rem_host TEXT, username TEXT, our_result BOOLEAN, upstream_result BOOLEAN, reason TEXT, solution TEXT, " +\
|
||||
"block_num INTEGER, prev_block_hash TEXT, useragent TEXT, difficulty INTEGER)")
|
||||
self.dbc.execute("create table shares_archive_found" +\
|
||||
"(id serial primary key,time timestamp,rem_host TEXT, username TEXT, our_result BOOLEAN, upstream_result BOOLEAN, reason TEXT, solution TEXT, " +\
|
||||
"block_num INTEGER, prev_block_hash TEXT, useragent TEXT, difficulty INTEGER)")
|
||||
self.dbc.execute("update pool set value = 5 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_5(self):
|
||||
log.info("running update 5")
|
||||
# Adding Primary key to table: pool
|
||||
self.dbc.execute("alter table pool add primary key (parameter)")
|
||||
self.dbh.commit()
|
||||
# Adjusting indicies on table: shares
|
||||
self.dbc.execute("DROP INDEX shares_username")
|
||||
self.dbc.execute("CREATE INDEX shares_time_username ON shares(time,username)")
|
||||
self.dbc.execute("CREATE INDEX shares_upstreamresult ON shares(upstream_result)")
|
||||
self.dbh.commit()
|
||||
|
||||
self.dbc.execute("update pool set value = 6 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_6(self):
|
||||
log.info("running update 6")
|
||||
|
||||
try:
|
||||
self.dbc.execute("CREATE EXTENSION pgcrypto")
|
||||
except psycopg2.ProgrammingError:
|
||||
log.info("pgcrypto already added to database")
|
||||
except psycopg2.OperationalError:
|
||||
raise Exception("Could not add pgcrypto extension to database. Have you got it installed? Ubuntu is postgresql-contrib")
|
||||
self.dbh.commit()
|
||||
|
||||
# Optimising table layout
|
||||
self.dbc.execute("ALTER TABLE pool " +\
|
||||
"ALTER COLUMN parameter TYPE character varying(128), ALTER COLUMN value TYPE character varying(512);")
|
||||
self.dbh.commit()
|
||||
|
||||
self.dbc.execute("UPDATE pool_worker SET password = encode(digest(concat(password, %s), 'sha1'), 'hex') WHERE id > 0", [self.salt])
|
||||
self.dbh.commit()
|
||||
|
||||
self.dbc.execute("ALTER TABLE pool_worker " +\
|
||||
"ALTER COLUMN username TYPE character varying(512), ALTER COLUMN password TYPE character(40), " +\
|
||||
"ADD CONSTRAINT username UNIQUE (username)")
|
||||
self.dbh.commit()
|
||||
|
||||
self.dbc.execute("update pool set value = 7 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
299
mining/DB_Sqlite.py
Normal file
299
mining/DB_Sqlite.py
Normal file
@ -0,0 +1,299 @@
|
||||
import time
|
||||
from stratum import settings
|
||||
import stratum.logger
|
||||
log = stratum.logger.get_logger('DB_Sqlite')
|
||||
|
||||
import sqlite3
|
||||
|
||||
class DB_Sqlite():
|
||||
def __init__(self):
|
||||
log.debug("Connecting to DB")
|
||||
self.dbh = sqlite3.connect(settings.DB_SQLITE_FILE)
|
||||
self.dbc = self.dbh.cursor()
|
||||
|
||||
def updateStats(self,averageOverTime):
|
||||
log.debug("Updating Stats")
|
||||
# Note: we are using transactions... so we can set the speed = 0 and it doesn't take affect until we are commited.
|
||||
self.dbc.execute("update pool_worker set speed = 0, alive = 0");
|
||||
stime = '%.2f' % ( time.time() - averageOverTime );
|
||||
self.dbc.execute("select username,SUM(difficulty) from shares where time > :time group by username", {'time':stime})
|
||||
total_speed = 0
|
||||
sqldata = []
|
||||
for name,shares in self.dbc.fetchall():
|
||||
speed = int(int(shares) * pow(2,32)) / ( int(averageOverTime) * 1000 * 1000)
|
||||
total_speed += speed
|
||||
sqldata.append({'speed':speed,'user':name})
|
||||
self.dbc.executemany("update pool_worker set speed = :speed, alive = 1 where username = :user",sqldata)
|
||||
self.dbc.execute("update pool set value = :val where parameter = 'pool_speed'",{'val':total_speed})
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_check(self):
|
||||
# Check for found shares to archive
|
||||
self.dbc.execute("select time from shares where upstream_result = 1 order by time limit 1")
|
||||
data = self.dbc.fetchone()
|
||||
if data is None or (data[0] + settings.ARCHIVE_DELAY) > time.time() :
|
||||
return False
|
||||
return data[0]
|
||||
|
||||
def archive_found(self,found_time):
|
||||
self.dbc.execute("insert into shares_archive_found select * from shares where upstream_result = 1 and time <= :time",{'time':found_time})
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_to_db(self,found_time):
|
||||
self.dbc.execute("insert into shares_archive select * from shares where time <= :time",{'time':found_time})
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_cleanup(self,found_time):
|
||||
self.dbc.execute("delete from shares where time <= :time",{'time':found_time})
|
||||
self.dbc.execute("vacuum")
|
||||
self.dbh.commit()
|
||||
|
||||
def archive_get_shares(self,found_time):
|
||||
self.dbc.execute("select * from shares where time <= :time",{'time':found_time})
|
||||
return self.dbc
|
||||
|
||||
def import_shares(self,data):
|
||||
log.debug("Importing Shares")
|
||||
# 0 1 2 3 4 5 6 7 8 9 10
|
||||
# data: [worker_name,block_header,block_hash,difficulty,timestamp,is_valid,ip,block_height,prev_hash,invalid_reason,share_diff]
|
||||
checkin_times = {}
|
||||
total_shares = 0
|
||||
best_diff = 0
|
||||
sqldata = []
|
||||
for k,v in enumerate(data):
|
||||
if settings.DATABASE_EXTEND :
|
||||
total_shares += v[3]
|
||||
if v[0] in checkin_times:
|
||||
if v[4] > checkin_times[v[0]] :
|
||||
checkin_times[v[0]]["time"] = v[4]
|
||||
else:
|
||||
checkin_times[v[0]] = {"time": v[4], "shares": 0, "rejects": 0 }
|
||||
|
||||
if v[5] == True :
|
||||
checkin_times[v[0]]["shares"] += v[3]
|
||||
else :
|
||||
checkin_times[v[0]]["rejects"] += v[3]
|
||||
|
||||
if v[10] > best_diff:
|
||||
best_diff = v[10]
|
||||
|
||||
sqldata.append({'time':v[4],'rem_host':v[6],'username':v[0],'our_result':v[5],'upstream_result':0,'reason':v[9],'solution':'',
|
||||
'block_num':v[7],'prev_block_hash':v[8],'ua':'','diff':v[3]} )
|
||||
else :
|
||||
sqldata.append({'time':v[4],'rem_host':v[6],'username':v[0],'our_result':v[5],'upstream_result':0,'reason':v[9],'solution':''} )
|
||||
|
||||
if settings.DATABASE_EXTEND :
|
||||
self.dbc.executemany("insert into shares " +\
|
||||
"(time,rem_host,username,our_result,upstream_result,reason,solution,block_num,prev_block_hash,useragent,difficulty) " +\
|
||||
"VALUES (:time,:rem_host,:username,:our_result,:upstream_result,:reason,:solution,:block_num,:prev_block_hash,:ua,:diff)",sqldata)
|
||||
|
||||
|
||||
self.dbc.execute("select value from pool where parameter = 'round_shares'")
|
||||
round_shares = int(self.dbc.fetchone()[0]) + total_shares
|
||||
self.dbc.execute("update pool set value = :val where parameter = 'round_shares'",{'val':round_shares})
|
||||
|
||||
self.dbc.execute("select value from pool where parameter = 'round_best_share'")
|
||||
round_best_share = int(self.dbc.fetchone()[0])
|
||||
if best_diff > round_best_share:
|
||||
self.dbc.execute("update pool set value = :val where parameter = 'round_best_share'",{'val':best_diff})
|
||||
|
||||
self.dbc.execute("select value from pool where parameter = 'bitcoin_difficulty'")
|
||||
difficulty = float(self.dbc.fetchone()[0])
|
||||
|
||||
if difficulty == 0:
|
||||
progress = 0
|
||||
else:
|
||||
progress = (round_shares/difficulty)*100
|
||||
self.dbc.execute("update pool set value = :val where parameter = 'round_progress'",{'val':progress})
|
||||
|
||||
sqldata = []
|
||||
for k,v in checkin_times.items():
|
||||
sqldata.append({'last_checkin':v["time"],'addshares':v["shares"],'addrejects':v["rejects"],'user':k})
|
||||
self.dbc.executemany("update pool_worker set last_checkin = :last_checkin, total_shares = total_shares + :addshares, " +\
|
||||
"total_rejects = total_rejects + :addrejects where username = :user",sqldata)
|
||||
else:
|
||||
self.dbc.executemany("insert into shares (time,rem_host,username,our_result,upstream_result,reason,solution) " +\
|
||||
"VALUES (:time,:rem_host,:username,:our_result,:upstream_result,:reason,:solution)",sqldata)
|
||||
|
||||
self.dbh.commit()
|
||||
|
||||
def found_block(self,data):
|
||||
# Note: difficulty = -1 here
|
||||
self.dbc.execute("update shares set upstream_result = :usr, solution = :sol where time = :time and username = :user",
|
||||
{'usr':data[5],'sol':data[2],'time':data[4],'user':data[0]})
|
||||
if settings.DATABASE_EXTEND and data[5] == True :
|
||||
self.dbc.execute("update pool_worker set total_found = total_found + 1 where username = :user",{'user':data[0]})
|
||||
self.dbc.execute("select value from pool where parameter = 'pool_total_found'")
|
||||
total_found = int(self.dbc.fetchone()[0]) + 1
|
||||
self.dbc.executemany("update pool set value = :val where parameter = :parm", [{'val':0,'parm':'round_shares'},
|
||||
{'val':0,'parm':'round_progress'},
|
||||
{'val':0,'parm':'round_best_share'},
|
||||
{'val':time.time(),'parm':'round_start'},
|
||||
{'val':total_found,'parm':'pool_total_found'}
|
||||
])
|
||||
self.dbh.commit()
|
||||
|
||||
def get_user(self, id_or_username):
|
||||
raise NotImplementedError('Not implemented for SQLite')
|
||||
|
||||
def list_users(self):
|
||||
raise NotImplementedError('Not implemented for SQLite')
|
||||
|
||||
def delete_user(self,id_or_username):
|
||||
raise NotImplementedError('Not implemented for SQLite')
|
||||
|
||||
def insert_user(self,username,password):
|
||||
log.debug("Adding Username/Password")
|
||||
self.dbc.execute("insert into pool_worker (username,password) VALUES (:user,:pass)", {'user':username,'pass':password})
|
||||
self.dbh.commit()
|
||||
|
||||
def update_user(self,username,password):
|
||||
raise NotImplementedError('Not implemented for SQLite')
|
||||
|
||||
def check_password(self,username,password):
|
||||
log.debug("Checking Username/Password")
|
||||
self.dbc.execute("select COUNT(*) from pool_worker where username = :user and password = :pass", {'user':username,'pass':password})
|
||||
data = self.dbc.fetchone()
|
||||
if data[0] > 0 :
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_worker_diff(self,username,diff):
|
||||
self.dbc.execute("update pool_worker set difficulty = :diff where username = :user",{'diff':diff,'user':username})
|
||||
self.dbh.commit()
|
||||
|
||||
def clear_worker_diff(self):
|
||||
if settings.DATABASE_EXTEND == True :
|
||||
self.dbc.execute("update pool_worker set difficulty = 0")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_pool_info(self,pi):
|
||||
self.dbc.executemany("update pool set value = :val where parameter = :parm",[{'val':pi['blocks'],'parm':"bitcoin_blocks"},
|
||||
{'val':pi['balance'],'parm':"bitcoin_balance"},
|
||||
{'val':pi['connections'],'parm':"bitcoin_connections"},
|
||||
{'val':pi['difficulty'],'parm':"bitcoin_difficulty"},
|
||||
{'val':time.time(),'parm':"bitcoin_infotime"}
|
||||
])
|
||||
self.dbh.commit()
|
||||
|
||||
def get_pool_stats(self):
|
||||
self.dbc.execute("select * from pool")
|
||||
ret = {}
|
||||
for data in self.dbc.fetchall():
|
||||
ret[data[0]] = data[1]
|
||||
return ret
|
||||
|
||||
def get_workers_stats(self):
|
||||
self.dbc.execute("select username,speed,last_checkin,total_shares,total_rejects,total_found,alive,difficulty from pool_worker")
|
||||
ret = {}
|
||||
for data in self.dbc.fetchall():
|
||||
ret[data[0]] = { "username" : data[0],
|
||||
"speed" : data[1],
|
||||
"last_checkin" : data[2],
|
||||
"total_shares" : data[3],
|
||||
"total_rejects" : data[4],
|
||||
"total_found" : data[5],
|
||||
"alive" : data[6],
|
||||
"difficulty" : data[7] }
|
||||
return ret
|
||||
|
||||
def close(self):
|
||||
self.dbh.close()
|
||||
|
||||
def check_tables(self):
|
||||
log.debug("Checking Tables")
|
||||
if settings.DATABASE_EXTEND == True :
|
||||
self.dbc.execute("create table if not exists shares" +\
|
||||
"(time DATETIME,rem_host TEXT, username TEXT, our_result INTEGER, upstream_result INTEGER, reason TEXT, solution TEXT, " +\
|
||||
"block_num INTEGER, prev_block_hash TEXT, useragent TEXT, difficulty INTEGER)")
|
||||
self.dbc.execute("create table if not exists pool_worker" +\
|
||||
"(username TEXT, password TEXT, speed INTEGER, last_checkin DATETIME)")
|
||||
self.dbc.execute("create table if not exists pool(parameter TEXT, value TEXT)")
|
||||
|
||||
self.dbc.execute("select COUNT(*) from pool where parameter = 'DB Version'")
|
||||
data = self.dbc.fetchone()
|
||||
if data[0] <= 0:
|
||||
self.dbc.execute("alter table pool_worker add total_shares INTEGER default 0")
|
||||
self.dbc.execute("alter table pool_worker add total_rejects INTEGER default 0")
|
||||
self.dbc.execute("alter table pool_worker add total_found INTEGER default 0")
|
||||
self.dbc.execute("insert into pool (parameter,value) VALUES ('DB Version',2)")
|
||||
self.update_tables()
|
||||
else :
|
||||
self.dbc.execute("create table if not exists shares" + \
|
||||
"(time DATETIME,rem_host TEXT, username TEXT, our_result INTEGER, upstream_result INTEGER, reason TEXT, solution TEXT)")
|
||||
self.dbc.execute("create table if not exists pool_worker(username TEXT, password TEXT)")
|
||||
self.dbc.execute("create index if not exists pool_worker_username ON pool_worker(username)")
|
||||
|
||||
def update_tables(self):
|
||||
version = 0
|
||||
current_version = 6
|
||||
while version < current_version :
|
||||
self.dbc.execute("select value from pool where parameter = 'DB Version'")
|
||||
data = self.dbc.fetchone()
|
||||
version = int(data[0])
|
||||
if version < current_version :
|
||||
log.info("Updating Database from %i to %i" % (version, version +1))
|
||||
getattr(self, 'update_version_' + str(version) )()
|
||||
|
||||
|
||||
def update_version_2(self):
|
||||
log.info("running update 2")
|
||||
self.dbc.executemany("insert into pool (parameter,value) VALUES (?,?)",[('bitcoin_blocks',0),
|
||||
('bitcoin_balance',0),
|
||||
('bitcoin_connections',0),
|
||||
('bitcoin_difficulty',0),
|
||||
('pool_speed',0),
|
||||
('pool_total_found',0),
|
||||
('round_shares',0),
|
||||
('round_progress',0),
|
||||
('round_start',time.time())
|
||||
])
|
||||
self.dbc.execute("create index if not exists shares_username ON shares(username)")
|
||||
self.dbc.execute("create index if not exists pool_worker_username ON pool_worker(username)")
|
||||
self.dbc.execute("update pool set value = 3 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_3(self):
|
||||
log.info("running update 3")
|
||||
self.dbc.executemany("insert into pool (parameter,value) VALUES (?,?)",[
|
||||
('round_best_share',0),
|
||||
('bitcoin_infotime',0),
|
||||
])
|
||||
self.dbc.execute("alter table pool_worker add alive INTEGER default 0")
|
||||
self.dbc.execute("update pool set value = 4 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_4(self):
|
||||
log.info("running update 4")
|
||||
self.dbc.execute("alter table pool_worker add difficulty INTEGER default 0")
|
||||
self.dbc.execute("create table if not exists shares_archive" +\
|
||||
"(time DATETIME,rem_host TEXT, username TEXT, our_result INTEGER, upstream_result INTEGER, reason TEXT, solution TEXT, " +\
|
||||
"block_num INTEGER, prev_block_hash TEXT, useragent TEXT, difficulty INTEGER)")
|
||||
self.dbc.execute("create table if not exists shares_archive_found" +\
|
||||
"(time DATETIME,rem_host TEXT, username TEXT, our_result INTEGER, upstream_result INTEGER, reason TEXT, solution TEXT, " +\
|
||||
"block_num INTEGER, prev_block_hash TEXT, useragent TEXT, difficulty INTEGER)")
|
||||
self.dbc.execute("update pool set value = 5 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
|
||||
def update_version_5(self):
|
||||
log.info("running update 5")
|
||||
# Adding Primary key to table: pool
|
||||
self.dbc.execute("alter table pool rename to pool_old")
|
||||
self.dbc.execute("create table if not exists pool(parameter TEXT, value TEXT, primary key(parameter))")
|
||||
self.dbc.execute("insert into pool select * from pool_old")
|
||||
self.dbc.execute("drop table pool_old")
|
||||
self.dbh.commit()
|
||||
# Adding Primary key to table: pool_worker
|
||||
self.dbc.execute("alter table pool_worker rename to pool_worker_old")
|
||||
self.dbc.execute("CREATE TABLE pool_worker(username TEXT, password TEXT, speed INTEGER, last_checkin DATETIME, total_shares INTEGER default 0, total_rejects INTEGER default 0, total_found INTEGER default 0, alive INTEGER default 0, difficulty INTEGER default 0, primary key(username))")
|
||||
self.dbc.execute("insert into pool_worker select * from pool_worker_old")
|
||||
self.dbc.execute("drop table pool_worker_old")
|
||||
self.dbh.commit()
|
||||
# Adjusting indicies on table: shares
|
||||
self.dbc.execute("DROP INDEX shares_username")
|
||||
self.dbc.execute("CREATE INDEX shares_time_username ON shares(time,username)")
|
||||
self.dbc.execute("CREATE INDEX shares_upstreamresult ON shares(upstream_result)")
|
||||
self.dbh.commit()
|
||||
|
||||
self.dbc.execute("update pool set value = 6 where parameter = 'DB Version'")
|
||||
self.dbh.commit()
|
||||
@ -87,7 +87,7 @@ class BasicShareLimiter(object):
|
||||
ts = int(timestamp)
|
||||
|
||||
# Init the stats for this worker if it isn't set.
|
||||
if worker_name not in self.worker_stats :
|
||||
if worker_name not in self.worker_stats or self.worker_stats[worker_name]['last_ts'] < ts - settings.DB_USERCACHE_TIME :
|
||||
self.worker_stats[worker_name] = {'last_rtc': (ts - self.retarget / 2), 'last_ts': ts, 'buffer': SpeedBuffer(self.buffersize) }
|
||||
dbi.update_worker_diff(worker_name, settings.POOL_TARGET)
|
||||
return
|
||||
@ -111,33 +111,58 @@ class BasicShareLimiter(object):
|
||||
avg = 1
|
||||
|
||||
# Figure out our Delta-Diff
|
||||
ddiff = float((float(current_difficulty) * (float(self.target) / float(avg))) - current_difficulty)
|
||||
if settings.VDIFF_FLOAT:
|
||||
ddiff = float((float(current_difficulty) * (float(self.target) / float(avg))) - current_difficulty)
|
||||
else:
|
||||
ddiff = int((float(current_difficulty) * (float(self.target) / float(avg))) - current_difficulty)
|
||||
|
||||
if (avg > self.tmax and current_difficulty > settings.VDIFF_MIN_TARGET):
|
||||
# For fractional -0.1 ddiff's just drop by 1
|
||||
if ddiff > -1:
|
||||
ddiff = -1
|
||||
# Don't drop below POOL_TARGET
|
||||
if (ddiff + current_difficulty) < settings.VDIFF_MIN_TARGET:
|
||||
ddiff = settings.VDIFF_MIN_TARGET - current_difficulty
|
||||
if settings.VDIFF_X2_TYPE:
|
||||
ddiff = 0.5
|
||||
# Don't drop below POOL_TARGET
|
||||
if (ddiff * current_difficulty) < settings.VDIFF_MIN_TARGET:
|
||||
ddiff = settings.VDIFF_MIN_TARGET / current_difficulty
|
||||
else:
|
||||
if ddiff > -1:
|
||||
ddiff = -1
|
||||
# Don't drop below POOL_TARGET
|
||||
if (ddiff + current_difficulty) < settings.POOL_TARGET:
|
||||
ddiff = settings.VDIFF_MIN_TARGET - current_difficulty
|
||||
elif avg < self.tmin:
|
||||
# For fractional 0.1 ddiff's just up by 1
|
||||
if ddiff < 1:
|
||||
ddiff = 1
|
||||
# Don't go above LITECOIN or VDIFF_MAX_TARGET
|
||||
self.update_litecoin_difficulty()
|
||||
if settings.USE_LITECOIN_DIFF:
|
||||
diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff])
|
||||
else:
|
||||
diff_max = settings.VDIFF_MAX_TARGET
|
||||
if settings.VDIFF_X2_TYPE:
|
||||
ddiff = 2
|
||||
# Don't go above LITECOIN or VDIFF_MAX_TARGET
|
||||
if settings.USE_LITECOIN_DIFF:
|
||||
self.update_litecoin_difficulty()
|
||||
diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff])
|
||||
else:
|
||||
diff_max = settings.VDIFF_MAX_TARGET
|
||||
|
||||
if (ddiff + current_difficulty) > diff_max:
|
||||
ddiff = diff_max - current_difficulty
|
||||
if (ddiff * current_difficulty) > diff_max:
|
||||
ddiff = diff_max / current_difficulty
|
||||
else:
|
||||
if ddiff < 1:
|
||||
ddiff = 1
|
||||
# Don't go above LITECOIN or VDIFF_MAX_TARGET
|
||||
if settings.USE_LITECOIN_DIFF:
|
||||
self.update_litecoin_difficulty()
|
||||
diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff])
|
||||
else:
|
||||
diff_max = settings.VDIFF_MAX_TARGET
|
||||
|
||||
if (ddiff + current_difficulty) > diff_max:
|
||||
ddiff = diff_max - current_difficulty
|
||||
|
||||
else: # If we are here, then we should not be retargeting.
|
||||
return
|
||||
|
||||
# At this point we are retargeting this worker
|
||||
new_diff = current_difficulty + ddiff
|
||||
if settings.VDIFF_X2_TYPE:
|
||||
new_diff = current_difficulty * ddiff
|
||||
else:
|
||||
new_diff = current_difficulty + ddiff
|
||||
log.info("Retarget for %s %i old: %i new: %i" % (worker_name, ddiff, current_difficulty, new_diff))
|
||||
|
||||
self.worker_stats[worker_name]['buffer'].clear()
|
||||
|
||||
@ -17,12 +17,21 @@ dbi.init_main()
|
||||
|
||||
class WorkerManagerInterface(object):
|
||||
def __init__(self):
|
||||
self.worker_log = {}
|
||||
self.worker_log.setdefault('authorized', {})
|
||||
return
|
||||
|
||||
def authorize(self, worker_name, worker_password):
|
||||
# Important NOTE: This is called on EVERY submitted share. So you'll need caching!!!
|
||||
return dbi.check_password(worker_name, worker_password)
|
||||
|
||||
def get_user_difficulty(self, worker_name):
|
||||
wd = dbi.get_user(worker_name)
|
||||
if len(wd) > 6:
|
||||
#dbi.update_worker_diff(worker_name, wd[6])
|
||||
return (True, wd[6])
|
||||
else:
|
||||
return (False, settings.POOL_TARGET)
|
||||
|
||||
class ShareLimiterInterface(object):
|
||||
'''Implement difficulty adjustments here'''
|
||||
|
||||
@ -51,10 +51,20 @@ class MiningService(GenericService):
|
||||
|
||||
if Interfaces.worker_manager.authorize(worker_name, worker_password):
|
||||
session['authorized'][worker_name] = worker_password
|
||||
is_ext_diff = False
|
||||
if settings.ALLOW_EXTERNAL_DIFFICULTY:
|
||||
(is_ext_diff, session['difficulty']) = Interfaces.worker_manager.get_user_difficulty(worker_name)
|
||||
self.connection_ref().rpc('mining.set_difficulty', [session['difficulty'], ], is_notification=True)
|
||||
else:
|
||||
session['difficulty'] = settings.POOL_TARGET
|
||||
# worker_log = (valid, invalid, is_banned, diff, is_ext_diff, timestamp)
|
||||
Interfaces.worker_manager.worker_log['authorized'][worker_name] = (0, 0, False, session['difficulty'], is_ext_diff, Interfaces.timestamper.time())
|
||||
return True
|
||||
else:
|
||||
if worker_name in session['authorized']:
|
||||
del session['authorized'][worker_name]
|
||||
if worker_name in Interfaces.worker_manager.worker_log['authorized']:
|
||||
del Interfaces.worker_manager.worker_log['authorized'][worker_name]
|
||||
return False
|
||||
|
||||
def subscribe(self, *args):
|
||||
@ -68,7 +78,6 @@ class MiningService(GenericService):
|
||||
session = self.connection_ref().get_session()
|
||||
session['extranonce1'] = extranonce1
|
||||
session['difficulty'] = settings.POOL_TARGET # Following protocol specs, default diff is 1
|
||||
|
||||
return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size)
|
||||
|
||||
def submit(self, worker_name, job_id, extranonce2, ntime, nonce):
|
||||
@ -88,26 +97,62 @@ class MiningService(GenericService):
|
||||
raise SubmitException("Connection is not subscribed for mining")
|
||||
|
||||
difficulty = session['difficulty']
|
||||
s_difficulty = difficulty
|
||||
submit_time = Interfaces.timestamper.time()
|
||||
ip = self.connection_ref()._get_ip()
|
||||
|
||||
Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, worker_name)
|
||||
(valid, invalid, is_banned, diff, is_ext_diff, last_ts) = Interfaces.worker_manager.worker_log['authorized'][worker_name]
|
||||
percent = float(float(invalid) / (float(valid) if valid else 1) * 100)
|
||||
|
||||
if is_banned and submit_time - last_ts > settings.WORKER_BAN_TIME:
|
||||
if percent > settings.INVALID_SHARES_PERCENT:
|
||||
log.debug("Worker invalid percent: %0.2f %s STILL BANNED!" % (percent, worker_name))
|
||||
else:
|
||||
is_banned = False
|
||||
log.debug("Clearing ban for worker: %s UNBANNED" % worker_name)
|
||||
(valid, invalid, is_banned, last_ts) = (0, 0, is_banned, Interfaces.timestamper.time())
|
||||
|
||||
if submit_time - last_ts > settings.WORKER_CACHE_TIME and not is_banned:
|
||||
if percent > settings.INVALID_SHARES_PERCENT and settings.ENABLE_WORKER_BANNING:
|
||||
is_banned = True
|
||||
log.debug("Worker invalid percent: %0.2f %s BANNED!" % (percent, worker_name))
|
||||
else:
|
||||
log.debug("Clearing worker stats for: %s" % worker_name)
|
||||
(valid, invalid, is_banned, last_ts) = (0, 0, is_banned, Interfaces.timestamper.time())
|
||||
|
||||
if 'prev_ts' in session and (submit_time - session['prev_ts']) < settings.VDIFF_RETARGET_DELAY \
|
||||
and not is_ext_diff:
|
||||
difficulty = session['prev_diff'] or session['difficulty'] or settings.POOL_TARGET
|
||||
diff = difficulty
|
||||
log.debug("%s (%d, %d, %s, %s, %d) %0.2f%% diff(%f)" % (worker_name, valid, invalid, is_banned, is_ext_diff, last_ts, percent, diff))
|
||||
if not is_ext_diff:
|
||||
Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, worker_name)
|
||||
|
||||
# This checks if submitted share meet all requirements
|
||||
# and it is valid proof of work.
|
||||
try:
|
||||
(block_header, block_hash, share_diff, on_submit) = Interfaces.template_registry.submit_share(job_id,
|
||||
worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty)
|
||||
worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, s_difficulty, submit_time)
|
||||
except SubmitException as e:
|
||||
# block_header and block_hash are None when submitted data are corrupted
|
||||
invalid += 1
|
||||
Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, diff, is_ext_diff, last_ts)
|
||||
|
||||
if is_banned:
|
||||
raise SubmitException("Worker is temporarily banned")
|
||||
|
||||
Interfaces.share_manager.on_submit_share(worker_name, False, False, difficulty,
|
||||
submit_time, False, ip, e[0], 0)
|
||||
submit_time, False, ip, e[0], 0)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
valid += 1
|
||||
Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, diff, is_ext_diff, last_ts)
|
||||
|
||||
if is_banned:
|
||||
raise SubmitException("Worker is temporarily banned")
|
||||
|
||||
Interfaces.share_manager.on_submit_share(worker_name, block_header,
|
||||
block_hash, difficulty, submit_time, True, ip, '', share_diff)
|
||||
|
||||
|
||||
if on_submit != None:
|
||||
# Pool performs submitblock() to litecoind. Let's hook
|
||||
# to result and report it to share manager
|
||||
|
||||
Loading…
Reference in New Issue
Block a user