Merge pull request #15 from ahmedbodi/issue-5

Issue 5
This commit is contained in:
ahmedbodi 2013-12-12 05:51:25 -08:00
commit 6d523a42fa
20 changed files with 1580 additions and 125 deletions

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
[submodule "externals/stratum"] [submodule "externals/stratum"]
path = externals/stratum path = externals/stratum
url = https://github.com/slush0/stratum.git url = https://github.com/slush0/stratum.git
[submodule "externals/quarkcoin-hash"]
path = externals/quarkcoin-hash
url = https://github.com/Neisklar/quarkcoin-hash-python

View File

@ -17,11 +17,16 @@ COINDAEMON_TRUSTED_USER = 'user'
COINDAEMON_TRUSTED_PASSWORD = 'somepassword' COINDAEMON_TRUSTED_PASSWORD = 'somepassword'
# Coin Algorithm is the option used to determine the algortithm used by stratum # Coin Algorithm is the option used to determine the algortithm used by stratum
# This currently only works with POW SHA256 and Scrypt Coins # This currently works with POW and POS coins
# The available options are scrypt and sha256d. # The available options are:
# scrypt, sha256d, scrypt-jane and quark
# 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 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_ALGO = 'scrypt'
COINDAEMON_Reward = 'POW'
COINDAEMON_SHA256_TX = 'no'
# ******************** BASIC SETTINGS *************** # ******************** BASIC SETTINGS ***************
# Backup Coin Daemon address's (consider having at least 1 backup) # Backup Coin Daemon address's (consider having at least 1 backup)
# You can have up to 99 # You can have up to 99
@ -83,12 +88,23 @@ PASSWORD_SALT = 'some_crazy_string'
# ******************** Database ********************* # ******************** 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 # MySQL
DB_MYSQL_HOST = 'localhost' DB_MYSQL_HOST = 'localhost'
DB_MYSQL_DBNAME = 'pooldb' DB_MYSQL_DBNAME = 'pooldb'
DB_MYSQL_USER = 'pooldb' DB_MYSQL_USER = 'pooldb'
DB_MYSQL_PASS = '**empty**' DB_MYSQL_PASS = '**empty**'
# ******************** Adv. DB Settings ********************* # ******************** Adv. DB Settings *********************
# Don't change these unless you know what you are doing # 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 INSTANCE_ID = 31 # Used for extranonce and needs to be 0-31
# ******************** Pool Difficulty Settings ********************* # ******************** 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) # 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) POOL_TARGET = 32 # Pool-wide difficulty target int >= 1
# 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
# Variable Difficulty Enable # Variable Difficulty Enable
VARIABLE_DIFF = True # Master variable difficulty enable VARIABLE_DIFF = True # Master variable difficulty enable
# Variable diff tuning variables # 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) #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_COINDAEMON_DIFF = False # Set the maximum difficulty to the coin daemon's difficulty. USE_LITECOIN_DIFF = False # Set the maximum difficulty to the litecoin difficulty.
DIFF_UPDATE_FREQUENCY = 86400 # Update the COINDAEMON difficulty once a day for the VARDIFF maximum DIFF_UPDATE_FREQUENCY = 86400 # Update the litecoin difficulty once a day for the VARDIFF maximum
VDIFF_MIN_TARGET = 15 # Minimum Target difficulty VDIFF_MIN_TARGET = 16 # Minimum Target difficulty
VDIFF_MAX_TARGET = 1000 # Maximum Target difficulty VDIFF_MAX_TARGET = 1024 # Maximum Target difficulty
VDIFF_TARGET_TIME = 30 # Target time per share (i.e. try to get 1 share per this many seconds) 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_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 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 ##### #### Advanced Option #####
# For backwards compatibility, we send the scrypt hash to the solutions column in the shares table # 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 # 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_PORT = 3333 # Getwork Proxy Port
GW_DISABLE_MIDSTATE = False # Disable midstate's (Faster but breaks some clients) 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) 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

@ -0,0 +1 @@
Subproject commit 8855c585d06f510134a8c66b686705f494bda0d9

View File

@ -124,7 +124,6 @@ class BitcoinRPCManager(object):
except: except:
self.next_connection() self.next_connection()
def getdifficulty(self): def getdifficulty(self):
while True: while True:
try: try:

View File

@ -19,6 +19,7 @@ class BlockTemplate(halfnode.CBlock):
coinbase_transaction_class = CoinbaseTransaction coinbase_transaction_class = CoinbaseTransaction
def __init__(self, timestamper, coinbaser, job_id): def __init__(self, timestamper, coinbaser, job_id):
print("Hit Block_template.py")
super(BlockTemplate, self).__init__() super(BlockTemplate, self).__init__()
self.job_id = job_id 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] + [ binascii.unhexlify(t['hash']) for t in data['transactions'] ]
txhashes = [None] + [ util.ser_uint256(int(t['hash'], 16)) for t in data['transactions'] ] txhashes = [None] + [ util.ser_uint256(int(t['hash'], 16)) for t in data['transactions'] ]
mt = merkletree.MerkleTree(txhashes) 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.height = data['height']
self.nVersion = data['version'] self.nVersion = data['version']
self.hashPrevBlock = int(data['previousblockhash'], 16) self.hashPrevBlock = int(data['previousblockhash'], 16)

View File

@ -9,59 +9,71 @@ log = lib.logger.get_logger('coinbaser')
# TODO: Add on_* hooks in the app # TODO: Add on_* hooks in the app
class SimpleCoinbaser(object): class SimpleCoinbaser(object):
'''This very simple coinbaser uses a constant coin address '''This very simple coinbaser uses constant bitcoin address
for all generated blocks.''' for all generated blocks.'''
def __init__(self, bitcoin_rpc, address): def __init__(self, bitcoin_rpc, address):
# Fire callback when coinbaser is ready print("hit the coinbaser")
self.on_load = defer.Deferred() # Fire Callback when the coinbaser is ready
self.on_load = defer.Deferred()
self.address = address self.address = address
self.is_valid = False # We need to check if pool can use this address self.is_valid = False
self.bitcoin_rpc = bitcoin_rpc self.bitcoin_rpc = bitcoin_rpc
self._validate() self._validate()
def _validate(self): def _validate(self):
d = self.bitcoin_rpc.validateaddress(self.address) d = self.bitcoin_rpc.validateaddress(self.address)
d.addCallback(self._address_check) if settings.COINDAEMON_Reward == 'POW':
d.addErrback(self._failure) d.addCallback(self._POW_address_check)
else: d.addCallback(self._POS_address_check)
def _address_check(self, result): d.addErrback(self._failure)
if result['isvalid'] and result['ismine']:
self.is_valid = True def _POW_address_check(self, result):
log.info("Coinbase address '%s' is valid" % self.address) if result['isvalid'] and result['ismine']:
self.is_valid = True
if not self.on_load.called: log.info("Coinbase address '%s' is valid" % self.address)
self.on_load.callback(True)
if not self.on_load.called:
elif result['isvalid'] and settings.ALLOW_NONLOCAL_WALLET == True : self.on_load.callback(True)
self.is_valid = True
log.warning("!!! Coinbase address '%s' is valid BUT it is not local" % self.address) elif result['isvalid'] and settings.ALLOW_NONLOCAL_WALLET == True :
self.is_valid = True
if not self.on_load.called: log.warning("!!! Coinbase address '%s' is valid BUT it is not local" % self.address)
self.on_load.callback(True)
if not self.on_load.called:
self.on_load.callback(True)
else: else:
self.is_valid = False self.is_valid = False
log.error("Coinbase address '%s' is NOT valid!" % self.address) log.error("Coinbase address '%s' is NOT valid!" % self.address)
def _failure(self, failure): def _POS_address_check(self, result):
log.error("Cannot validate Bitcoin address '%s'" % self.address) print(result)
raise 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): #def on_new_block(self):
# pass # pass
#def on_new_template(self): #def on_new_template(self):
# pass # pass
def _failure(self, failure):
log.error("Cannot validate Bitcoin address '%s'" % self.address)
raise
def get_script_pubkey(self): def get_script_pubkey(self):
if not self.is_valid: if settings.COINDAEMON_Reward == 'POW':
# Try again, maybe the coind was down? if not self.is_valid:
self._validate() self._validate()
raise Exception("Coinbase address is not validated!") raise Exception("Wallet Address is Wrong")
return util.script_to_address(self.address) return util.script_to_address(self.address)
else:
return util.script_to_pubkey(self.pubkey)
def get_coinbase_data(self): def get_coinbase_data(self):
return '' return ''

View File

@ -2,8 +2,9 @@ import binascii
import halfnode import halfnode
import struct import struct
import util import util
import settings
class CoinbaseTransaction(halfnode.CTransaction): if settings.COINDAEMON_Reward == 'POW':
class CoinbaseTransaction(halfnode.CTransaction):
'''Construct special transaction used for coinbase tx. '''Construct special transaction used for coinbase tx.
It also implements quick serialization using pre-cached It also implements quick serialization using pre-cached
scriptSig template.''' scriptSig template.'''
@ -46,4 +47,97 @@ class CoinbaseTransaction(halfnode.CTransaction):
raise Exception("Incorrect extranonce size") raise Exception("Incorrect extranonce size")
(part1, part2) = self.vin[0]._scriptSig_template (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

View File

@ -111,11 +111,17 @@ COINDAEMON_TRUSTED_PORT = 8332 # RPC port
COINDAEMON_TRUSTED_USER = 'stratum' COINDAEMON_TRUSTED_USER = 'stratum'
COINDAEMON_TRUSTED_PASSWORD = '***somepassword***' COINDAEMON_TRUSTED_PASSWORD = '***somepassword***'
# Coin Algorithm is the option used to determine the algortithm used by stratum # Coin Algorithm is the option used to determine the algortithm used by stratum
# This currently only works with POW SHA256 and Scrypt Coins # This currently only works with POW SHA256 and Scrypt Coins
# The available options are scrypt and sha256d. # 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_ALGO = 'scrypt'
COINDAEMON_Reward = 'POW'
COINDAEMON_SHA256_TX = 'yes'
# ******************** OTHER CORE SETTINGS ********************* # ******************** OTHER CORE SETTINGS *********************
# Use "echo -n '<yourpassword>' | sha256sum | cut -f1 -d' ' " # Use "echo -n '<yourpassword>' | sha256sum | cut -f1 -d' ' "

View File

@ -19,10 +19,27 @@ import settings
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
print("########################################### Loading LTC Scrypt Module #########################################################") print("########################################### Loading LTC Scrypt Module #########################################################")
import ltc_scrypt import ltc_scrypt
elif settings.COINDAEMON_ALGO == 'quark':
print("########################################### Loading Quark Module #########################################################")
import quark_hash
else: else:
print("########################################### NOT Loading LTC Scrypt Module ######################################################") print("########################################### NOT Loading LTC Scrypt Module ######################################################")
pass 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 import lib.logger
log = lib.logger.get_logger('halfnode') log = lib.logger.get_logger('halfnode')
@ -138,25 +155,74 @@ class CTxOut(object):
class CTransaction(object): class CTransaction(object):
def __init__(self): def __init__(self):
self.nVersion = 1 if settings.COINDAEMON_Reward == 'POW':
self.vin = [] self.nVersion = 1
self.vout = [] self.vin = []
self.nLockTime = 0 self.vout = []
self.sha256 = None 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): def deserialize(self, f):
self.nVersion = struct.unpack("<i", f.read(4))[0] if settings.COINDAEMON_Reward == 'POW':
self.vin = deser_vector(f, CTxIn) self.nVersion = struct.unpack("<i", f.read(4))[0]
self.vout = deser_vector(f, CTxOut) self.vin = deser_vector(f, CTxIn)
self.nLockTime = struct.unpack("<I", f.read(4))[0] self.vout = deser_vector(f, CTxOut)
self.sha256 = None 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): def serialize(self):
r = "" if settings.COINDAEMON_Reward == 'POW':
r += struct.pack("<i", self.nVersion) r = ""
r += ser_vector(self.vin) r += struct.pack("<i", self.nVersion)
r += ser_vector(self.vout) r += ser_vector(self.vin)
r += struct.pack("<I", self.nLockTime) r += ser_vector(self.vout)
return r 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): def calc_sha256(self):
if self.sha256 is None: if self.sha256 is None:
self.sha256 = uint256_from_str(SHA256.new(SHA256.new(self.serialize()).digest()).digest()) self.sha256 = uint256_from_str(SHA256.new(SHA256.new(self.serialize()).digest()).digest())
@ -183,7 +249,13 @@ class CBlock(object):
self.sha256 = None self.sha256 = None
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
self.scrypt = None self.scrypt = None
elif settings.COINDAEMON_ALGO == 'quark':
self.quark = None
else: pass else: pass
if settings.COINDAEMON_Reward == 'POS':
self.signature = b""
else: pass
def deserialize(self, f): def deserialize(self, f):
self.nVersion = struct.unpack("<i", f.read(4))[0] self.nVersion = struct.unpack("<i", f.read(4))[0]
self.hashPrevBlock = deser_uint256(f) self.hashPrevBlock = deser_uint256(f)
@ -192,6 +264,9 @@ class CBlock(object):
self.nBits = struct.unpack("<I", f.read(4))[0] self.nBits = struct.unpack("<I", f.read(4))[0]
self.nNonce = struct.unpack("<I", f.read(4))[0] self.nNonce = struct.unpack("<I", f.read(4))[0]
self.vtx = deser_vector(f, CTransaction) self.vtx = deser_vector(f, CTransaction)
if settings.COINDAEMON_Reward == 'POS':
self.signature = deser_string(f)
else: pass
def serialize(self): def serialize(self):
r = [] r = []
r.append(struct.pack("<i", self.nVersion)) 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.nBits))
r.append(struct.pack("<I", self.nNonce)) r.append(struct.pack("<I", self.nNonce))
r.append(ser_vector(self.vtx)) r.append(ser_vector(self.vtx))
if settings.COINDAEMON_Reward == 'POS':
r.append(ser_string(self.signature))
else: pass
return ''.join(r) return ''.join(r)
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
@ -215,6 +293,18 @@ class CBlock(object):
r.append(struct.pack("<I", self.nNonce)) r.append(struct.pack("<I", self.nNonce))
self.scrypt = uint256_from_str(ltc_scrypt.getPoWHash(''.join(r))) self.scrypt = uint256_from_str(ltc_scrypt.getPoWHash(''.join(r)))
return self.scrypt 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: else:
def calc_sha256(self): def calc_sha256(self):
if self.sha256 is None: if self.sha256 is None:
@ -232,12 +322,17 @@ class CBlock(object):
def is_valid(self): def is_valid(self):
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
self.calc_scrypt() self.calc_scrypt()
elif settings.COINDAEMON_ALGO == 'quark':
self.calc_quark()
else: else:
self.calc_sha256() self.calc_sha256()
target = uint256_from_compact(self.nBits) target = uint256_from_compact(self.nBits)
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
if self.scrypt > target: if self.scrypt > target:
return false return false
elif settings.COINDAEMON_ALGO == 'quark':
if self.quark > target:
return false
else: else:
if self.sha256 > target: if self.sha256 > target:
return False return False

43
lib/notify_email.py Normal file
View 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])

View File

@ -5,6 +5,10 @@ import StringIO
import settings import settings
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
import ltc_scrypt import ltc_scrypt
elif settings.COINDAEMON_ALGO == 'scrypt-jane':
import yac_scrypt
elif settings.COINDAEMON_ALGO == 'quark':
import quark_hash
else: pass else: pass
from twisted.internet import defer from twisted.internet import defer
from lib.exceptions import SubmitException from lib.exceptions import SubmitException
@ -131,7 +135,8 @@ class TemplateRegistry(object):
start = Interfaces.timestamper.time() start = Interfaces.timestamper.time()
template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) 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']) self.add_template(template,data['height'])
log.info("Update finished, %.03f sec, %d txes" % \ log.info("Update finished, %.03f sec, %d txes" % \
@ -144,6 +149,8 @@ class TemplateRegistry(object):
'''Converts difficulty to target''' '''Converts difficulty to target'''
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000
elif settings.COINDAEMON_ALGO == 'quark':
diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000
else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000
return diff1 / difficulty return diff1 / difficulty
@ -169,7 +176,7 @@ class TemplateRegistry(object):
return j return j
def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, 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 '''Check parameters and finalize block template. If it leads
to valid block candidate, asynchronously submits the block to valid block candidate, asynchronously submits the block
back to the bitcoin network. back to the bitcoin network.
@ -228,13 +235,19 @@ class TemplateRegistry(object):
# 4. Reverse header and compare it with target of the user # 4. Reverse header and compare it with target of the user
if settings.COINDAEMON_ALGO == 'scrypt': 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) ])) 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) hash_int = util.uint256_from_str(hash_bin)
scrypt_hash_hex = "%064x" % hash_int scrypt_hash_hex = "%064x" % hash_int
header_hex = binascii.hexlify(header_bin) 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" 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) target_user = self.diff_to_target(difficulty)
if hash_int > target_user and \ 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']) ): or 'prev_diff' not in session or hash_int > self.diff_to_target(session['prev_diff']) ):
raise SubmitException("Share is above target") 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 # Mostly for debugging purposes
target_info = self.diff_to_target(100000) target_info = self.diff_to_target(100000)
if hash_int <= target_info: if hash_int <= target_info:

View 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)

View File

@ -3,6 +3,8 @@
import struct import struct
import StringIO import StringIO
import binascii import binascii
import settings
import bitcoin_rpc
from hashlib import sha256 from hashlib import sha256
def deser_string(f): def deser_string(f):
@ -171,7 +173,7 @@ def address_to_pubkeyhash(addr):
addr = b58decode(addr, 25) addr = b58decode(addr, 25)
except: except:
return None return None
if addr is None: if addr is None:
return None return None
@ -209,9 +211,15 @@ def ser_number(n):
s.append(n) s.append(n)
return bytes(s) return bytes(s)
def script_to_address(addr): if settings.COINDAEMON_Reward == 'POW':
d = address_to_pubkeyhash(addr) def script_to_address(addr):
if not d: d = address_to_pubkeyhash(addr)
raise ValueError('invalid address') if not d:
(ver, pubkeyhash) = d raise ValueError('invalid address')
return b'\x76\xa9\x14' + pubkeyhash + b'\x88\xac' (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'

View File

@ -39,14 +39,33 @@ class DBInterface():
self.bitcoinrpc = bitcoinrpc self.bitcoinrpc = bitcoinrpc
def connectDB(self): def connectDB(self):
if settings.VARIABLE_DIFF: if settings.DATABASE_DRIVER == "sqlite":
log.debug("DB_Mysql_Vardiff INIT") log.debug('DB_Sqlite INIT')
import DB_Mysql_Vardiff import DB_Sqlite
return DB_Mysql_Vardiff.DB_Mysql_Vardiff() return DB_Sqlite.DB_Sqlite()
else: elif settings.DATABASE_DRIVER == "mysql":
log.debug('DB_Mysql INIT') if settings.VARIABLE_DIFF:
import DB_Mysql log.debug("DB_Mysql_Vardiff INIT")
return DB_Mysql.DB_Mysql() 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): def clearusercache(self):
log.debug("DBInterface.clearusercache called") log.debug("DBInterface.clearusercache called")
@ -55,7 +74,9 @@ class DBInterface():
def scheduleImport(self): def scheduleImport(self):
# This schedule's the Import # This schedule's the Import
use_thread = True if settings.DATABASE_DRIVER == "sqlite":
use_thread = False
else: use_thread = True
if use_thread: if use_thread:
self.queueclock = reactor.callLater(settings.DB_LOADER_CHECKTIME , self.run_import_thread) self.queueclock = reactor.callLater(settings.DB_LOADER_CHECKTIME , self.run_import_thread)

54
mining/DB_None.py Normal file
View 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
View 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
View 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()

View File

@ -87,7 +87,7 @@ class BasicShareLimiter(object):
ts = int(timestamp) ts = int(timestamp)
# Init the stats for this worker if it isn't set. # 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) } 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) dbi.update_worker_diff(worker_name, settings.POOL_TARGET)
return return
@ -111,33 +111,58 @@ class BasicShareLimiter(object):
avg = 1 avg = 1
# Figure out our Delta-Diff # 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): if (avg > self.tmax and current_difficulty > settings.VDIFF_MIN_TARGET):
# For fractional -0.1 ddiff's just drop by 1 # For fractional -0.1 ddiff's just drop by 1
if ddiff > -1: if settings.VDIFF_X2_TYPE:
ddiff = -1 ddiff = 0.5
# Don't drop below POOL_TARGET # Don't drop below POOL_TARGET
if (ddiff + current_difficulty) < settings.VDIFF_MIN_TARGET: if (ddiff * current_difficulty) < settings.VDIFF_MIN_TARGET:
ddiff = settings.VDIFF_MIN_TARGET - current_difficulty 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: elif avg < self.tmin:
# For fractional 0.1 ddiff's just up by 1 # For fractional 0.1 ddiff's just up by 1
if ddiff < 1: if settings.VDIFF_X2_TYPE:
ddiff = 1 ddiff = 2
# Don't go above LITECOIN or VDIFF_MAX_TARGET # Don't go above LITECOIN or VDIFF_MAX_TARGET
self.update_litecoin_difficulty() if settings.USE_LITECOIN_DIFF:
if settings.USE_LITECOIN_DIFF: self.update_litecoin_difficulty()
diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff]) diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff])
else: else:
diff_max = settings.VDIFF_MAX_TARGET diff_max = settings.VDIFF_MAX_TARGET
if (ddiff + current_difficulty) > diff_max: if (ddiff * current_difficulty) > diff_max:
ddiff = diff_max - current_difficulty 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. else: # If we are here, then we should not be retargeting.
return return
# At this point we are retargeting this worker # 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)) log.info("Retarget for %s %i old: %i new: %i" % (worker_name, ddiff, current_difficulty, new_diff))
self.worker_stats[worker_name]['buffer'].clear() self.worker_stats[worker_name]['buffer'].clear()

View File

@ -17,12 +17,21 @@ dbi.init_main()
class WorkerManagerInterface(object): class WorkerManagerInterface(object):
def __init__(self): def __init__(self):
self.worker_log = {}
self.worker_log.setdefault('authorized', {})
return return
def authorize(self, worker_name, worker_password): def authorize(self, worker_name, worker_password):
# Important NOTE: This is called on EVERY submitted share. So you'll need caching!!! # Important NOTE: This is called on EVERY submitted share. So you'll need caching!!!
return dbi.check_password(worker_name, worker_password) 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): class ShareLimiterInterface(object):
'''Implement difficulty adjustments here''' '''Implement difficulty adjustments here'''

View File

@ -51,10 +51,20 @@ class MiningService(GenericService):
if Interfaces.worker_manager.authorize(worker_name, worker_password): if Interfaces.worker_manager.authorize(worker_name, worker_password):
session['authorized'][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 return True
else: else:
if worker_name in session['authorized']: if worker_name in session['authorized']:
del session['authorized'][worker_name] 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 return False
def subscribe(self, *args): def subscribe(self, *args):
@ -68,7 +78,6 @@ class MiningService(GenericService):
session = self.connection_ref().get_session() session = self.connection_ref().get_session()
session['extranonce1'] = extranonce1 session['extranonce1'] = extranonce1
session['difficulty'] = settings.POOL_TARGET # Following protocol specs, default diff is 1 session['difficulty'] = settings.POOL_TARGET # Following protocol specs, default diff is 1
return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size) return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size)
def submit(self, worker_name, job_id, extranonce2, ntime, nonce): 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") raise SubmitException("Connection is not subscribed for mining")
difficulty = session['difficulty'] difficulty = session['difficulty']
s_difficulty = difficulty
submit_time = Interfaces.timestamper.time() submit_time = Interfaces.timestamper.time()
ip = self.connection_ref()._get_ip() ip = self.connection_ref()._get_ip()
(valid, invalid, is_banned, diff, is_ext_diff, last_ts) = Interfaces.worker_manager.worker_log['authorized'][worker_name]
Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, 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 # This checks if submitted share meet all requirements
# and it is valid proof of work. # and it is valid proof of work.
try: try:
(block_header, block_hash, share_diff, on_submit) = Interfaces.template_registry.submit_share(job_id, (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: except SubmitException as e:
# block_header and block_hash are None when submitted data are corrupted # 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, 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 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, Interfaces.share_manager.on_submit_share(worker_name, block_header,
block_hash, difficulty, submit_time, True, ip, '', share_diff) block_hash, difficulty, submit_time, True, ip, '', share_diff)
if on_submit != None: if on_submit != None:
# Pool performs submitblock() to litecoind. Let's hook # Pool performs submitblock() to litecoind. Let's hook
# to result and report it to share manager # to result and report it to share manager