POW and POS support via a config change

This commit is contained in:
Ahmed Bodiwala 2013-11-21 17:03:31 +00:00
parent e266c97d8b
commit b19af3e9ac
9 changed files with 463 additions and 70 deletions

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,50 @@ 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
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

@ -138,25 +138,49 @@ 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
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
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
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())
@ -184,6 +208,10 @@ class CBlock(object):
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
self.scrypt = None self.scrypt = 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 +220,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 +232,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':

View File

@ -131,7 +131,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" % \

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

@ -125,7 +125,7 @@ class BasicShareLimiter(object):
ddiff = 1 ddiff = 1
# Don't go above LITECOIN or VDIFF_MAX_TARGET # Don't go above LITECOIN or VDIFF_MAX_TARGET
self.update_litecoin_difficulty() self.update_litecoin_difficulty()
if settings.USE_LITECOIN_DIFF: if settings.USE_COINDAEMON_DIFF:
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