Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions _main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from params import version
from log_module import logger


#TODO
# - rounding errors in balance calculation, example: from_account=1084190
# - wallet connection crashes sometimes after mining-submit
Expand All @@ -17,6 +18,7 @@
class WalletNotFoundError(Exception):
pass


def wallet_notify_watchdog():
if client.last_miner_notify_flag is False:
try:
Expand All @@ -25,10 +27,14 @@ def wallet_notify_watchdog():
logger.error("WALLET notify watchdog error: " + str(e))
pass
client.last_miner_notify_flag = False
threading.Timer(client.last_miner_notify_timeout, wallet_notify_watchdog, []).start()
threading.Timer(client.last_miner_notify_timeout, wallet_notify_watchdog,
[]).start()


print("Starting MicroCoin mining pool by vegtamas. Pool version: " + str(version))
logger.info("Starting MicroCoin mining pool by vegtamas. Pool version: " + str(version))
print("Starting MicroCoin mining pool by vegtamas. Pool version: " +
str(version))
logger.info("Starting MicroCoin mining pool by vegtamas. Pool version: " +
str(version))

while True:
print("Waiting for wallet sync")
Expand All @@ -43,7 +49,7 @@ def wallet_notify_watchdog():
thread_client = threading.Thread(target=client.client_handler)
thread_client.start()

if(wallet_json_rpc.wallet_ok):
if (wallet_json_rpc.wallet_ok):
server.start_diff_servers()

#thread_mining_notify = threading.Thread(target=server.send_mining_notify_to_all)
Expand Down
38 changes: 25 additions & 13 deletions accountancy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
account_fees = {}
current_block = 0


class Payment_batch():
"""
Payment batch class:
Expand Down Expand Up @@ -39,7 +40,7 @@ def new_block_accountancy():

if not sqlite_handler.db.is_block_in_db_already(current_block):
calc_shares()
calc_share_rates(current_block, current_block*5)
calc_share_rates(current_block, current_block * 5)


def calc_shares():
Expand Down Expand Up @@ -67,16 +68,20 @@ def calc_share_rates(last_block, from_account):
mining.shares_of_current_block = 0
new_payment_batch.add_payment(pool_account, 0)

new_payment_batch_text = "New payment batch: block: " + str(new_payment_batch.block) + ", from account: " + str(
new_payment_batch.from_account) + '\n'
new_payment_batch_text = "New payment batch: block: " + str(
new_payment_batch.block) + ", from account: " + str(
new_payment_batch.from_account) + '\n'
for payment in new_payment_batch.payments:
text = "To: " + str(payment) + ", " + str(new_payment_batch.payments[payment]) + '\n'
text = "To: " + str(payment) + ", " + str(
new_payment_batch.payments[payment]) + '\n'
new_payment_batch_text = new_payment_batch_text + text
new_payment_batch_text += '\n'

for payment in new_payment_batch.payments:
try:
sqlite_handler.db.add_payment_to_DB(last_block, from_account, payment, new_payment_batch.payments[payment])
sqlite_handler.db.add_payment_to_DB(
last_block, from_account, payment,
new_payment_batch.payments[payment])
except Exception as e:
logger.error("SQlite error at calc_share_rates: " + str(e))
print("SQlite error")
Expand All @@ -95,20 +100,25 @@ def set_amounts(block):
if payment[3] == pool_account:
continue
if payment[3] not in account_fees:
account_fees[payment[3]] = pool_fee # if there was a restart after account goes offline, there is no fee data
account_fees[payment[
3]] = pool_fee # if there was a restart after account goes offline, there is no fee data

from_account = payment[2]
to_account = payment[3]
amount = round((payment[8] * block_reward * (1 - (account_fees[payment[3]] / 100)) - payment_fee - payment_fee_to_pool), payment_prec)
amount = round((payment[8] * block_reward *
(1 - (account_fees[payment[3]] / 100)) - payment_fee -
payment_fee_to_pool), payment_prec)
if amount > payment_fee:
sqlite_handler.db.set_amount_for_payment(payment[1], payment[2], payment[3], amount)
sqlite_handler.db.set_amount_for_payment(payment[1], payment[2],
payment[3], amount)
spent += amount + payment_fee
else:
sqlite_handler.db.remove_payment_from_DB(from_account, to_account)

amount = round(block_reward - spent - payment_fee, payment_prec)
if amount > payment_fee:
sqlite_handler.db.set_amount_for_payment(block, from_account, pool_account, amount)
sqlite_handler.db.set_amount_for_payment(block, from_account,
pool_account, amount)
else:
sqlite_handler.db.remove_payment_from_DB(from_account, pool_account)

Expand All @@ -135,8 +145,10 @@ def payment_processor():
if retval:
sqlite_handler.db.set_block_to_acked_by_wallet(block[1])
set_amounts(block[1])
elif block[1] < current_block - orphan_age_limit: # check if the block is orphan
sqlite_handler.db.set_block_to_orphan(block[1]) # set to orphan in db
elif block[
1] < current_block - orphan_age_limit: # check if the block is orphan
sqlite_handler.db.set_block_to_orphan(
block[1]) # set to orphan in db
print("Block %d marked as orphan" % block[1])

result = sqlite_handler.db.get_unconfirmed_blocks()
Expand Down Expand Up @@ -165,15 +177,15 @@ def payment_processor():
try:
wallet_json_rpc.send_payment(row[2], row[3], row[4], row[1])
except wallet_json_rpc.WalletPubKeyError:
if row[1] < current_block - orphan_age_limit: # block is orphan
if row[1] < current_block - orphan_age_limit: # block is orphan
sqlite_handler.db.set_block_to_orphan(row[1])
except wallet_json_rpc.WalletCommError:
return False
except wallet_json_rpc.WalletInvalidTargetAccountError:
# TODO handle invalid target account. But if it's validated on auth, then no need for that.
logger.info("Invalid target account: " + str(row[3]))
except wallet_json_rpc.WalletInvalidOperationError:
pass # TODO it's probably a balance issue which occurs rarely. Sometimes payouts fails and rewards are sent twice for an account and there is no money left for the rest.
pass # TODO it's probably a balance issue which occurs rarely. Sometimes payouts fails and rewards are sent twice for an account and there is no money left for the rest.
else:
sqlite_handler.db.set_payment_to_paid(row[1], row[2], row[3])

Expand Down
4 changes: 3 additions & 1 deletion client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ def client_handler():
last_miner_notify_flag = True

if "result" in msg and "pow" in msg["result"]:
print("NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE")
print(
"NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE NEW BLOCK FOUND!! YEEEE"
)
accountancy.new_block_accountancy()


Expand Down
3 changes: 2 additions & 1 deletion log_module.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging

logger = logging.getLogger("protopool")
hdlr = logging.handlers.TimedRotatingFileHandler('./protopool.log', when='midnight')
hdlr = logging.handlers.TimedRotatingFileHandler('./protopool.log',
when='midnight')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
Expand Down
40 changes: 28 additions & 12 deletions mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@

shares_of_current_block = 0

miners = {} # "account": number_of_shares
shares = {} # dict of pplns_shares objects, every account has an element + pool has one
miners = {} # "account": number_of_shares
shares = {
} # dict of pplns_shares objects, every account has an element + pool has one
# TODO miner object for every miner/account,
# collect every miner related function to that

# TODO future feature: collect shares worker-by-worker for detailed stat
hr_shares = {} # shares log for hashrate calculation for each account; example = {"1111":{"1":[timestamp1, timestamp2, timestamp3, ...], "32":[timestamp1, timestamp2, timestamp3, ..]}}
hr_avrg_shares = 30 # number of shares to calculated average hashrate
hr_shares = {
} # shares log for hashrate calculation for each account; example = {"1111":{"1":[timestamp1, timestamp2, timestamp3, ...], "32":[timestamp1, timestamp2, timestamp3, ..]}}
hr_avrg_shares = 30 # number of shares to calculated average hashrate

share_timeout = 240 # shares older than this will be deleted

share_timeout = 240 # shares older than this will be deleted

class miner_conn():
"""Miner connection class. Stores a miner with all of it's details"""
Expand All @@ -24,19 +27,25 @@ def __init__(self, connection, address):
self.addr = address
self.timestamps = {}
self.account = ""

def set_account(self, account):
self.account = account

def add_share(self, timestamp, difficulty):
self.timestamps[timestamp] = difficulty


miner_conns = []


def print_stat():
"""Prints pool statistics every minute"""
global shares_of_current_block, miner_conns
print("Number of connected miners: " + str(len(miner_conns)) + " Running threads: " + str(threading.active_count()))
print("Number of connected miners: " + str(len(miner_conns)) +
" Running threads: " + str(threading.active_count()))
threading.Timer(60, print_stat).start()


def add_share_for_hr_calc(account, difficulty):
"""Adds shares to global list of hr_shares for hashrate calculation"""
global hr_shares
Expand All @@ -59,6 +68,7 @@ def add_share_for_hr_calc(account, difficulty):

hr_shares[account][difficulty].append(time.time())


def get_hr(account):
"""
Get hashrate for an account.
Expand All @@ -70,19 +80,21 @@ def get_hr(account):

global hr_shares

account = str(account) # using account as str
account = str(account) # using account as str

if account not in hr_shares:
return 0

diffs = []
for diff in server.get_server_diffs(): # using diffs as str
for diff in server.get_server_diffs(): # using diffs as str
diffs.append(str(diff))

hrs = [] # hashrate for every difficulty, then sum them for account hashrate
hrs = [
] # hashrate for every difficulty, then sum them for account hashrate

for diff in diffs:
if len(hr_shares[account][diff]) < 2: # can't calculate from 0 or 1 shares
if len(hr_shares[account]
[diff]) < 2: # can't calculate from 0 or 1 shares
continue

# delete old shares
Expand All @@ -93,13 +105,15 @@ def get_hr(account):
new_timestamps.append(ts)
hr_shares[account][diff] = new_timestamps

if len(hr_shares[account][diff]) < 2: # can't calculate from 0 or 1 shares
if len(hr_shares[account]
[diff]) < 2: # can't calculate from 0 or 1 shares
continue

# get average share time
last_share = hr_shares[account][diff][-1]
first_share = hr_shares[account][diff][0]
avrg_time = (last_share - first_share) / (len(hr_shares[account][diff]) - 1)
avrg_time = (last_share -
first_share) / (len(hr_shares[account][diff]) - 1)
new_hr = float(diff) * 2**32 / avrg_time
hrs.append(new_hr)

Expand All @@ -108,6 +122,7 @@ def get_hr(account):
sum_hr += hr
return sum_hr


def get_pool_hr():
"""Gets pool hashrate by adding up individual hashrates"""
global hr_shares
Expand All @@ -118,6 +133,7 @@ def get_pool_hr():

return pool_hr


def No_miners():
"""Returns the number of active miner connections"""
return len(miner_conns)
9 changes: 5 additions & 4 deletions params.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
version = 2.0

# user config
payment_fee_to_pool = config_data["payment_fee_to_pool"] # this fee will be sent to pool address in order to prevent tx errors occured by rounding
payment_fee_to_pool = config_data[
"payment_fee_to_pool"] # this fee will be sent to pool address in order to prevent tx errors occured by rounding
pool_fee = config_data["pool_fee"]
pool_account = config_data["pool_account"]
payment_fee = config_data["payment_fee"]
pplns_interval = config_data["pplns_interval"] # in secs
pplns_interval = config_data["pplns_interval"] # in secs

wallet_jsonrpc_ip = config_data["wallet_jsonrpc_ip"]
wallet_jsonrpc_port = config_data["wallet_jsonrpc_port"]
Expand All @@ -21,6 +22,6 @@
main_db_file = config_data["main_db_file"]

# other configs
orphan_age_limit = 20 # after how many blocks should the pool mark a block as orphan
orphan_age_limit = 20 # after how many blocks should the pool mark a block as orphan
payment_prec = 4
maturation_time = 10 # in blocks
maturation_time = 10 # in blocks
51 changes: 36 additions & 15 deletions restapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,48 +48,69 @@ def transfer_account_handler(new_pubkey):
last_account_transferred_block = current_block
return acc_number


@app.route('/pool_data', methods=['GET'])
def get_pool_data():
network_height = wallet_json_rpc.get_current_block()
pool_data = {
"current_block": str(network_height),
"net_hashrate": str(wallet_json_rpc.get_net_hashrate(network_height)) + " Gh",
"net_hashrate":
str(wallet_json_rpc.get_net_hashrate(network_height)) + " Gh",
"algorithm": "Pascal",
"poolhash": str(round(mining.get_pool_hr() / 10**9, 2)) + " Gh",
"nethash": 0,
"workers": str(mining.No_miners()),
"fee": str(pool_fee) + "%",
"period": "Every block"
"nethash": 0,
"workers": str(mining.No_miners()),
"fee": str(pool_fee) + "%",
"period": "Every block"
}
return jsonify({'pool_data': pool_data})


@app.route('/miner_data/<int:account>', methods=['GET'])
def get_miner_data(account):
miner_data = {
"account": str(account),
"hashrate": str(round(mining.get_hr(account) / 10**9, 3)) + " Gh",
"1hour": 0,
"24hours": 0,
"average_mined": 0,
"payments": sqlite_handler.db.get_account_payments(account)
"1hour": 0,
"24hours": 0,
"average_mined": 0,
"payments": sqlite_handler.db.get_account_payments(account)
}
return jsonify({'miner_data': miner_data})


@app.route('/get_account', methods=['POST'])
def get_account():
pubkey = request.get_json(force=True)['pubkey']
try:
acc_number = transfer_account_handler(pubkey)
except wallet_json_rpc.WalletCommError:
return jsonify({'result': 'An error occured on the pool side. Please try again. If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'})
return jsonify({
'result':
'An error occured on the pool side. Please try again. If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'
})
except BePatientError:
return jsonify({'result': 'An account was already sent to someone in this block. Please try again in a few minutes. If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'})
return jsonify({
'result':
'An account was already sent to someone in this block. Please try again in a few minutes. If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'
})
except wallet_json_rpc.NoEmptyAccountError:
return jsonify({'result': 'No free account is left on the pool. Please try again in a few minutes. If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'})
return jsonify({
'result':
'No free account is left on the pool. Please try again in a few minutes. If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'
})
except wallet_json_rpc.InputParameterError:
return jsonify({'result': 'Wrong public key! You can export your public key from the wallet. It has to start with "3G". If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'})
return jsonify({
'result':
'Wrong public key! You can export your public key from the wallet. It has to start with "3G". If you see this message multiple times please report it to the team on Discord or Telegram. Thank you!'
})

return jsonify({
'result':
'Account ' + str(acc_number) +
' was successfully sent to your public key. You will see it in the next block in appr. 5 minutes.'
})

return jsonify({'result': 'Account ' + str(acc_number) + ' was successfully sent to your public key. You will see it in the next block in appr. 5 minutes.'})

def start_restapi():
app.run(debug=False, port = 3000)
app.run(debug=False, port=3000)
Loading