def post(self, id, field, value): global database #database = lib.get_db() LOGGER = lib.get_logger(PROCESS) # AUTH FILTER if id != g.user.id: response = jsonify( {'message': 'Not authorized to access data for other users'}) response.status_code = 403 return response debug and LOGGER.warn( "WorkerAPI_utxo post id:{} field:{} value:{}".format( id, field, value)) allowed_fields = ["address", "method"] if field not in allowed_fields: response = jsonify({'message': 'Invalid field for update'}) response.status_code = 403 return response st = False if field == "address": st = Pool_utxo.update_address(id, value) elif field == "method": st = Pool_utxo.update_method(id, value) if st == False: response = jsonify( {'message': 'Failed to update {}'.format(field)}) response.status_code = 500 return response else: response = jsonify({field: value}) response.status_code = 200 return response
def submit_tx_slate(user_id, slate, logger, database): if slate is None: message = "No slate data provided" logger.warn(message) raise PaymentError(400, message) try: slate_json = json.loads(slate.decode('utf-8')) tx_id = slate_json["id"] except Exception as e: message = "Invalid slate data provided" logger.warn(message) raise PaymentError(400, message) try: logger.warn("Running submit_slate: tx_id = {}".format(tx_id)) # Record Keeping timestamp = datetime.utcnow() locked_utxo = Pool_utxo.get_locked_by_userid(user_id) locked_utxo.last_success = timestamp locked_utxo.failure_count = 0 payment_rec = Pool_payment.get_by_address(tx_id) payment_rec.state = "posted" finalized_slate = wallet.finalize_tx(slate_json) payment_rec.tx_data = json.dumps(finalized_slate) database.db.getSession().commit() except Exception as e: logger.exception("Unexpected Error in submit_tx_slate") raise PaymentError(500, str(e)) # Post the TX try: wallet.post_tx(finalized_slate["tx"]) except Exception as e: logger.exception("Failed to post payment: {}".format(repr(e))) raise PaymentError(500, str(e))
def get(self, id=None, fields=None): database = lib.get_db() LOGGER = lib.get_logger(PROCESS) LOGGER.warn("WorkerAPI_payments get id:{} fields:{}".format( id, fields)) fields = lib.fields_to_list(fields) utxo = Pool_utxo.get_by_address(id) return utxo.to_json(fields)
def main(): global LOGGER global CONFIG CONFIG = lib.get_config() LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Get Config settings pool_fee = float(CONFIG[PROCESS]["pool_fee"]) # Number of blocks of share data used to calculate rewards PPLNG_WINDOW_SIZE = 60 try: PPLNG_WINDOW_SIZE = int(os.environ["PPLNG_WINDOW_SIZE"]) except Exception as e: LOGGER.error( "Failed to get PPLNG_WINDOW_SIZE from the environment: {} Using default size of {}" .format(e, PPLNG_WINDOW_SIZE)) # Connect to DB database = lib.get_db() # Get current blockchain height chain_height = grin.blocking_get_current_height() # Get unlocked blocks from the db unlocked_blocks = Pool_blocks.get_all_unlocked() unlocked_blocks = [blk.height for blk in unlocked_blocks] LOGGER.warn("Paying for {} pool blocks: {}".format(len(unlocked_blocks), unlocked_blocks)) for height in unlocked_blocks: try: LOGGER.warn("Processing unlocked block: {}".format(height)) # Call the library routine to get this blocks payout map payout_map = pool.calculate_block_payout_map( height, PPLNG_WINDOW_SIZE, pool_fee, LOGGER, False) #print("payout_map = {}".format(payout_map)) # Store the payment map for this block credits_record = Pool_credits(chain_height, height, payout_map) database.db.getSession().add(credits_record) # Make payments based on the workers total share_value Pool_blocks.setState(height, "paid") for user_id, payment_amount in payout_map.items(): # Add worker rewards to pool account balance LOGGER.warn("Credit to user: {} = {}".format( user_id, payment_amount)) worker_utxo = Pool_utxo.credit_worker(user_id, payment_amount) # Worker_stats accounting and running totals #latest_worker_stats = Worker_stats.get_latest_by_id(user_id) #latest_worker_stats.dirty = True database.db.getSession().commit() except Exception as e: database.db.getSession().rollback() LOGGER.exception("Something went wrong: {}".format(repr(e))) LOGGER.warn("=== Completed {}".format(PROCESS)) sys.stdout.flush()
def main(): global LOGGER LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB database = lib.get_db() latest_block = 0 # XXX All in one db transaction.... # Get unlocked blocks from the db unlocked_blocks = Pool_blocks.get_all_unlocked() database.db.getSession().commit() for pb in unlocked_blocks: try: LOGGER.warn("Processing unlocked block: {}".format(pb)) if pb.height > latest_block: latest_block = pb.height # Get valid pool_shares for that block from the db pool_shares = Pool_shares.get_valid_by_height(pb.height) # Calculate Payment info: worker_shares = {} for ps in pool_shares: LOGGER.warn("Processing pool_shares: {}".format(ps)) # Need to get actual_difficulty gs = Grin_shares.get_by_nonce(ps.nonce) if gs == None: # XXX NOTE: no payout for shares not accepted by grin node continue if ps.found_by in worker_shares: worker_shares[ps.found_by] += gs.actual_difficulty else: worker_shares[ps.found_by] = gs.actual_difficulty if len(worker_shares) > 0: # Calcualte reward/difficulty: XXX TODO: Enhance # What algorithm to use? Maybe: https://slushpool.com/help/manual/rewards r_per_d = REWARD / sum(worker_shares.values()) for worker in worker_shares.keys(): # Calculate reward per share worker_rewards = worker_shares[worker] * r_per_d # Add or create worker rewards worker_utxo = Pool_utxo.credit_worker( worker, worker_rewards) LOGGER.warn("Credit to user: {} = {}".format( worker, worker_rewards)) # Mark the pool_block state="paid" (maybe "processed" would be more accurate?) pb.state = "paid" database.db.getSession().commit() except Exception as e: database.db.getSession().rollback() LOGGER.error("Something went wrong: {}".format(e)) #database.db.getSession().commit() # db.set_last_run(PROCESS, str(time.time())) LOGGER.warn("=== Completed {}".format(PROCESS)) sys.stdout.flush()
def create(cls, username, password): if username is None or password is None: return None username = username.lower() try: user_rec = Users(username, password) database.db.createDataObj(user_rec) # Create the users utxo record pool_utxo = Pool_utxo(user_rec.id) database.db.createDataObj(pool_utxo) return user_rec except: # XXX TODO: Log err return None
def get(self, id, fields=None): global database #database = lib.get_db() LOGGER = lib.get_logger(PROCESS) # AUTH FILTER if id != g.user.id: response = jsonify({ 'message': 'Not authorized to access data for other users' }) response.status_code = 403 return response debug and LOGGER.warn("WorkerAPI_utxo get id:{} fields:{}".format(id, fields)) fields = lib.fields_to_list(fields) utxo = Pool_utxo.get_by_userid(id) if utxo is None: return None return utxo.to_json(fields)
def main(): global LOGGER global CONFIG CONFIG = lib.get_config() LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB database = lib.get_db() # Configs minimum_payout = int(CONFIG[PROCESS]["minimum_payout"]) walletauth = (wallet_api_user, wallet_api_key) utxos = Pool_utxo.getPayable(minimum_payout) # XXX TODO: Use the current balance, timestamp, the last_attempt timestamp, last_payout, and failed_attempts # XXX TODO: to filter and sort by order we want to make payment attempts for utxo in utxos: try: # Try less often for wallets that dont answer if utxo.amount < utxo.failure_count: if randint(0, 11) != 0: continue LOGGER.warn( "Processing utxo for: {} {} {} using method: {}".format( utxo.user_id, utxo.address, utxo.amount, utxo.method)) if utxo.method in ["http", "https", "keybase"]: try: #user_id, address, logger, database, wallet_auth, method, invoked_by payments.atomic_send(utxo.user_id, utxo.address, LOGGER, database, walletauth, utxo.method, "schedule") except payments.PaymentError as e: LOGGER.error("Failed to make http payment: {}".format(e)) else: LOGGER.warn( "Automatic payment does not (yet?) support method: {}". format(utxo.method)) except Exception as e: LOGGER.error("Failed to process utxo: {} because {}".format( utxo.user_id, str(e))) database.db.getSession().rollback() sys.exit(1) LOGGER.warn("=== Completed {}".format(PROCESS))
def main(): global LOGGER LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB database = lib.get_db() latest_block = 0 # XXX All in one db transaction.... # Get unlocked blocks from the db unlocked_blocks = Pool_blocks.get_all_unlocked() database.db.getSession().commit() for pb in unlocked_blocks: try: LOGGER.warn("Processing unlocked block: {}".format(pb)) if pb.height > latest_block: latest_block = pb.height # Get Worker_stats of this block to calculate reward for each worker worker_stats = Worker_stats.get_by_height(pb.height) # Calculate Payment info: if len(worker_stats) > 0: # Calcualte reward/share: # XXX TODO: Enhance # What algorithm to use? Maybe: https://slushpool.com/help/manual/rewards r_per_g = REWARD / sum([st.gps for st in worker_stats]) for stat in worker_stats: # Calculate reward worker_rewards = stat.gps * r_per_g # Add or create worker rewards worker_utxo = Pool_utxo.credit_worker( stat.worker, worker_rewards) LOGGER.warn("Credit to user: {} = {}".format( stat.worker, worker_rewards)) # Mark the pool_block state="paid" (maybe "processed" would be more accurate?) pb.state = "paid" database.db.getSession().commit() except Exception as e: database.db.getSession().rollback() LOGGER.error("Something went wrong: {}".format(e)) #database.db.getSession().commit() LOGGER.warn("=== Completed {}".format(PROCESS)) sys.stdout.flush()
def main(): global LOGGER LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB database = lib.get_db() # XXX All in one db transaction.... # Get unlocked blocks from the db unlocked_blocks = Pool_blocks.get_all_unlocked() unlocked_blocks = [blk.height for blk in unlocked_blocks] for height in unlocked_blocks: try: LOGGER.warn("Processing unlocked block: {}".format(height)) # Call the library routine to get this blocks payout map payout_map = pool.calculate_block_payout_map( height, PPLNS_WINDOW, LOGGER, False) #print("payout_map = {}".format(payout_map)) # Make payments based on the workers total share_value Pool_blocks.setState(height, "paid") database.db.getSession().commit() for user_id, payment_amount in payout_map.items(): # Add worker rewards to pool account balance LOGGER.warn("Credit to user: {} = {}".format( user_id, payment_amount)) worker_utxo = Pool_utxo.credit_worker(user_id, payment_amount) # Worker_stats accounting and running totals #latest_worker_stats = Worker_stats.get_latest_by_id(user_id) #latest_worker_stats.dirty = True database.db.getSession().commit() except Exception as e: database.db.getSession().rollback() LOGGER.error("Something went wrong: {} - {}".format( e, traceback.print_exc())) LOGGER.warn("=== Completed {}".format(PROCESS)) sys.stdout.flush()
def cancel_tx_slate(tx_slate_id, new_state, logger, database): try: logger.warn("In cancel_tx_slate") # For tx sent via slate, tx_id is in the pool_payment.address field payment_rec = Pool_payment.get_by_address(tx_slate_id) if payment_rec is None: message = "Could not find any payment record for tx_slate_id {}".format( tx_slate_id) logger.warn(message) raise PaymentError(400, message) # Check if the wallet already has this marked as canceled wallet_rec = wallet.retrieve_txs(tx_slate_id=tx_slate_id) if len(wallet_rec) == 0: logger.warn( "Wallet has no record of tx_slate_id: {}".format(tx_slate_id)) else: logger.warn("XXX: wallet_rec = {}".format(wallet_rec)) assert wallet_rec[0][ "tx_slate_id"] == tx_slate_id, "Wallet returned incorrect tx data: {} vs {}".format( wallet_rec[0]["tx_slate_id"], tx_slate_id) if wallet_rec[0]["tx_type"] == "TxSentCancelled": logger.warn( "Tx already marked canceled: {}".format(tx_slate_id)) else: wallet.cancel_tx(tx_slate_id=tx_slate_id) # Mark payment record state as expired or canceled payment_rec.state = new_state # Credit back the user utxo amount locked_utxo = Pool_utxo.get_locked_by_userid(payment_rec.user_id) locked_utxo.amount = locked_utxo.amount + payment_rec.amount + payment_rec.fee locked_utxo.failure_count += payment_rec.failure_count + 1 database.db.getSession().commit() except Exception as e: logger.exception("Unexpected Error in cancel_tx_slate: {}".format( str(e))) raise PaymentError(500, str(e))
def post(self, id, function, address=None): global database LOGGER = lib.get_logger(PROCESS) debug = True debug and LOGGER.warn("PoolAPI_paymentrequest POST: {} - {}".format(id, function)) # AUTH FILTER if id != g.user.id: response = jsonify({ 'message': 'Not authorized to access data for other users' }) response.status_code = 403 return response # XXX TODO: Get from config payment_req_url = "http://grinwallet:13425" # Get the users balance then call the internal payment request api to # generate a payment tx slate. Return that slate to the caller if function == "get_tx_slate": # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #LOGGER.warn("SLATE payment requests are disabled") #response = jsonify({ 'message': 'File-Based payouts are temporarily disabled' }) #response.status_code = 400 #return response # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ## # Offline Phase 1) issue_send_tx # Generate the send transaction slate utxo = Pool_utxo.get_by_userid(id) amount = utxo.amount user_id = str(utxo.user_id) # XXX TODO: Check if greater than minimum payout get_tx_slate_url = payment_req_url + "/pool/payment/get_tx_slate/"+user_id debug and LOGGER.warn("Requesting Payment slate: {}".format(get_tx_slate_url)) r = requests.post( url=get_tx_slate_url, auth=(admin_user, admin_pass) ) debug and LOGGER.warn("get_tx_slate call: {} - {}".format(r.status_code, r.reason)) if r.status_code != 200: LOGGER.warn("Failed to get a payment slate: {} - {} - {}".format(r.status_code, r.reason, r.json()["message"])) response = jsonify({ 'message': 'Failed to get a payment slate: {}'.format(r.json()["message"])}) response.status_code = 400 return response return(json.loads(r.text)) elif function == "submit_tx_slate": ## # Offline Phase 2) finalize_tx # Submit the signed slate to be finalized debug and LOGGER.warn("submit_slate: {}".format(id)) try: requestdata = request.data rdjson = json.loads(requestdata.decode('utf-8')) debug and LOGGER.warn("PoolAPI_paymentrequest POST: requestdata:{}".format(rdjson)) except AttributeError as e: LOGGER.warn("Missing tx_slate data - {}".format(request.data)) response = jsonify({ 'message': 'Missing signed slate data' }) response.status_code = 400 return response except json.decoder.JSONDecodeError: LOGGER.warn("Invalid tx_slate data - {}".format(request.data)) response = jsonify({ 'message': 'Invalid signed slate data was submitted' }) response.status_code = 400 return response debug and LOGGER.warn("submit_slate: {}".format(requestdata)) submit_tx_slate_url = payment_req_url + "/pool/payment/submit_tx_slate/"+str(id) r = requests.post( url=submit_tx_slate_url, data=requestdata, auth=(admin_user, admin_pass) ) if r.status_code != 200: #LOGGER.warn("Failed to submit payment slate: {} - {} - {}".format(r.status_code, r.reason, r.json()["message"])) #response = jsonify({ 'message': 'Failed to submit payment slate: {}'.format(r.json()["message"]) }) LOGGER.warn("Failed to submit payment slate: {} - {}".format(r.status_code, r.reason)) response = jsonify({ 'message': 'Failed to submit payment slate: {}'.format(r.reason) }) response.status_code = 500 return response debug and LOGGER.warn("submit_tx_slate result: {} - {}".format(r.status_code, r.text)) return "ok" elif function == "http": ## # Online Wallet-To-Wallet debug and LOGGER.warn("Send HTTP transaction: {}".format(id)) LOGGER.warn("request.args: {}".format(request.args)) if address is None: LOGGER.warn("HTTP payment request missing address") response = jsonify({ 'message': 'Error, must specify a wallet address:port' }) response.status_code = 400 return response debug and LOGGER.warn("Initiate HTTP payment: {} - {} - {}".format(id, address, request.args)) LOGGER.warn("Args in json: {}".format(json.dumps(request.args))) http_payment_url = payment_req_url + "/pool/payment/http/{}/{}".format(id, address) r = requests.post( url=http_payment_url, data=json.dumps(request.args), headers={'content-type': 'application/json'}, auth=(admin_user, admin_pass) ) if r.status_code != 200: LOGGER.warn("Failed to complete HTTP payment: {} - {}".format(r.status_code, r.reason)) response = jsonify({ 'message': 'Failed to complete HTTP payment: {}'.format(r.json()["message"]) }) response.status_code = 400 return response debug and LOGGER.warn("http payment result: {} - {}".format(r.status_code, r.text)) return "ok" elif function == "keybase": ## # Online Wallet-To-Keybase-To-Keybase-To-Wallet debug and LOGGER.warn("Send keybase transaction: {}".format(id)) if address is None: LOGGER.warn("keybase payment request missing address") response = jsonify({ 'message': 'Error, must specify a keybase username to send to' }) response.status_code = 400 return response debug and LOGGER.warn("Initiate keybase payment: {} - {}".format(id, address)) keybase_payment_url = payment_req_url + "/pool/payment/keybase/{}/{}".format(id, address) r = requests.post( url=keybase_payment_url, auth=(admin_user, admin_pass) ) if r.status_code != 200: LOGGER.warn("Failed to complete keybase payment: {} - {}".format(r.status_code, r.reason)) response = jsonify({ 'message': 'Failed to complete keybase payment: {}'.format(r.json()["message"]) }) response.status_code = 400 return response debug and LOGGER.warn("keybase payment result: {} - {}".format(r.status_code, r.text)) return "ok" elif function == "payout_script": ## # Not really a payout request, rather, a request for payout automation script code debug and LOGGER.warn("Get Payout Script: {}".format(id)) #file = open("/content/BGP_payout.py", "r") #payout_script = file.read() #return payout_script return send_from_directory('/content', 'BGP_payout.py') else: LOGGER.warn("Invalid Payment Type requested") response = jsonify({ 'message': 'Error, must specify valid payment request method. Method {} is not valid.'.format(function) }) response.status_code = 400 return response
print("Invalid state choice") sys.exit(1) # Get the payout record payout_record = Pool_payment.get_by_id(payout_id) print("{}".format(payout_record)) amount = payout_record.amount / float(NANOGRIN) # Get the user id user_id = payout_record.user_id user_record = Users.get_by_id(user_id) username = user_record.username print("User: {}".format(username)) # Get the users UTXO record user_utxo = Pool_utxo.get_by_userid(user_id) # Print a report print("Will update account for {} on {}".format(username, poolname)) print( "Will cancel payout {} and add {} to users current balance of {} for a new balance of {}" .format(payout_id, amount, float(user_utxo.amount) / NANOGRIN, float(user_utxo.amount) / NANOGRIN + amount)) # Confirm action print("") proceed_or_exit() # Do it payout_record.state = new_state
def get(self, id=None, fields=None): database = lib.get_db() fields = lib.fields_to_list(fields) utxo = Pool_utxo.get_by_address(id) return utxo.to_json(fields)
debit_pool = "x" while debit_pool != "n" and debit_pool != "y": debit_pool = input("Debit the pools account? (y/n): ") if debit_pool != "n" and debit_pool != "y": print("Invalid input") # Get the user id user_id = Users.get_id_by_username(username) if user_id == 0: print("Could not find user: {} in the database".format(username)) sys.exit(1) #user_record = Users.get_by_id(user_id) #print(user_record) # Get the users UTXO record user_utxo = Pool_utxo.get_by_userid(user_id) # Get the pools UTXO if needed if debit_pool == "y": pooladmin_utxo = Pool_utxo.get_by_userid(1) # Print a report print("Will update account for {} on {}".format(username, poolname)) print("Will add {} to users current balance of {} for a new balance of {}". format(amount, float(user_utxo.amount) / NANOGRIN, float(user_utxo.amount) / NANOGRIN + amount)) if debit_pool == "y": print( "Will subtract {} from pool admin current balance of {} for a new balance of {}" .format(amount, float(pooladmin_utxo.amount) / NANOGRIN,
def main(): global LOGGER global CONFIG CONFIG = lib.get_config() LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # DB connection details db_host = CONFIG["db"]["address"] + ":" + CONFIG["db"]["port"] db_user = CONFIG["db"]["user"] db_password = CONFIG["db"]["password"] db_name = CONFIG["db"]["db_name"] mysqlcontsraints = MysqlConstants(db_host, db_user, db_password, db_name) # Connect to DB database.db = database_details(MYSQL_CONSTANTS=mysqlcontsraints) database.db.initialize() wallet_dir = CONFIG[PROCESS]["wallet_dir"] minimum_payout = int(CONFIG[PROCESS]["minimum_payout"]) os.chdir(wallet_dir) utxos = Pool_utxo.getPayable(minimum_payout) database.db.getSession().commit() # XXX TODO: Use the current balance, timestamp, the last_attempt timestamp, last_payout, and failed_attempts # XXX TODO: to filter and sort by order we want to make payment attempts for utxo in utxos: try: LOGGER.warn("Trying to pay: {} {} {}".format( utxo.id, utxo.address, utxo.amount)) # Lock just this current record for update locked_utxo = Pool_utxo.get_locked_by_id(utxo.id) # Save and Zero the balance original_balance = locked_utxo.amount locked_utxo.amount = 0 # Savepoint changes - if we crash after sending coins but before commit we roll back to here. # The pool audit service finds lost payouts and restores user balance database.db.getSession().begin_nested() # Attempt to make the payment timestamp = "{:%B %d, %Y %H:%M:%S.%f}".format(datetime.now()) status = makePayout(locked_utxo.address, original_balance) LOGGER.warn("Payout status: {}".format(status)) if status == 0: LOGGER.warn("Made payout for {} {} {}".format( locked_utxo.id, locked_utxo.address, original_balance)) # Update timestamp of last payout, number of failed payout attempts locked_utxo.amount = 0 locked_utxo.failure_count = 0 locked_utxo.last_try = timestamp locked_utxo.last_success = timestamp # Commit changes database.db.getSession().commit() else: LOGGER.error("Failed to make payout: {} {} {}".format( locked_utxo.id, locked_utxo.address, original_balance)) # Restore the users balance locked_utxo.amount = original_balance # Update number of failed payout attempts if locked_utxo.failure_count is None: locked_utxo.failure_count = 0 locked_utxo.failure_count += 1 locked_utxo.last_try = timestamp # Commit changes database.db.getSession().commit() database.db.getSession().commit() except Exception as e: LOGGER.error("Failed to process utxo: {} because {}".format( utxo.id, str(e))) database.db.getSession().rollback() sys.exit(1) LOGGER.warn("=== Completed {}".format(PROCESS))
def atomic_send(user_id, address, logger, database, method, invoked_by): # validate method if method not in ["http", "https", "keybase"]: message = "Invalid payment method requested" logger.warn(message) raise PaymentError(400, message) # Validate Address address = address.lstrip().rstrip() if address is None: message = "Wallet address is missing" logger.warn(message) raise PaymentError(400, message) if method == "http" or method == "https": if not address.startswith("http"): address = method + "://" + address valid = validateAddress(address, method, logger) if valid == False: message = "Wallet address is invalid: {}".format(address) logger.warn(message) raise PaymentError(400, message) if method == "http" or method == "https": probe = testWalletPort(address, logger) if probe == False: message = "Failed to establish connection with remote wallet listener at: {}".format( address) logger.warn(message) raise PaymentError(400, message) # Lock this utxo record for update and check for minimum balance amount = 0 try: locked_utxo = Pool_utxo.get_locked_by_userid(user_id) if locked_utxo is None or locked_utxo.amount < (1 * 1000000000): message = "Insufficient available balance for payout" logger.warn(message) raise PaymentError(400, message) # Save the users current balance amount = locked_utxo.amount except PaymentError as e: # My own errors raise except Exception as e: logger.exception("Failed to get worker balance: {}".format(str(e))) raise PaymentError(500, str(e)) # Call the synchronous send method # Subtract the balance from UTXO slate = None try: timestamp = datetime.utcnow() # Send the TX if method == "http" or method == "https": slate = http_send(user_id, address, amount, logger) elif method == "keybase": slate = keybase_send(user_id, address, amount, logger) # Create a payment record payment_rec = Pool_payment( user_id=locked_utxo.user_id, timestamp=timestamp, height=slate["height"], address=str(address), amount=locked_utxo.amount, method=method, fee=int(slate["fee"]), failure_count=locked_utxo.failure_count, state="posted", tx_data=json.dumps(slate), invoked_by=invoked_by, ) database.db.getSession().add(payment_rec) # Update the users utxo record locked_utxo.amount = int(slate["fee"]) * -1 locked_utxo.last_try = timestamp locked_utxo.last_success = timestamp locked_utxo.total_amount += amount # Commit this database.db.getSession().commit() except PaymentError as e: # My own errors logger.exception("Failed to send tx".format(repr(e))) if slate is not None: wallet.cancel_tx(tx_slate_id=slate["id"]) raise e except Exception as e: # All other (unexpected) errors logger.exception("Failed to create payment because {}".format(repr(e))) raise PaymentError(500, str(e)) # Post the TX try: logger.warn("Debug: Post the TX") wallet.post_tx(slate["tx"]) except Exception as e: # All other (unexpected) errors # Tidy will handle the refund to utxo # Tidy will mark payment record expired # Tidy will call cancel_tx_slate to unlock the wallet outputs logger.exception("Failed to post payment because {}".format(repr(e))) raise PaymentError(500, str(e))
def main(): global LOGGER global CONFIG CONFIG = lib.get_config() LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB try: database = lib.get_db() except Exception as e: LOGGER.error("Failed to connect to the db: {}".format(e)) wallet_dir = CONFIG[PROCESS]["wallet_dir"] minimum_payout = int(CONFIG[PROCESS]["minimum_payout"]) os.chdir(wallet_dir) utxos = Pool_utxo.getPayable(minimum_payout) database.db.getSession().commit() # XXX TODO: Use the current balance, timestamp, the last_attempt timestamp, last_payout, and failed_attempts # XXX TODO: to filter and sort by order we want to make payment attempts for utxo in utxos: try: # Try less often for wallets that dont answer if utxo.amount < utxo.failure_count: if randint(0, 11) != 0: continue LOGGER.warn("Trying to pay: {} {} {}".format(utxo.id, utxo.address, utxo.amount)) # Lock just this current record for update locked_utxo = Pool_utxo.get_locked_by_id(utxo.id) # Save and Zero the balance original_balance = locked_utxo.amount locked_utxo.amount = 0 # Savepoint changes - if we crash after sending coins but before commit we roll back to here. # The pool audit service (coming soon) finds lost payouts and restores user balance database.db.getSession().begin_nested(); # Attempt to make the payment timestamp = datetime.utcnow() status = makePayout(locked_utxo.address, original_balance) LOGGER.warn("Payout status: {}".format(status)) if status == 0: LOGGER.warn("Made payout for {} {} {} at {}".format(locked_utxo.id, locked_utxo.address, original_balance, timestamp)) # Create a payment record payment_record = Pool_payment(locked_utxo.id, timestamp, locked_utxo.address, original_balance, 0, locked_utxo.failure_count, "schedule" ) database.db.getSession().add(payment_record) # Update timestamp of last payout, number of failed payout attempts locked_utxo.amount = 0 locked_utxo.failure_count = 0 locked_utxo.last_try = timestamp locked_utxo.last_success = timestamp locked_utxo.total_amount += original_balance # Commit changes database.db.getSession().commit() else: LOGGER.error("Failed to make payout: {} {} {}".format(locked_utxo.id, locked_utxo.address, original_balance)) # Restore the users balance locked_utxo.amount = original_balance # Update number of failed payout attempts if locked_utxo.failure_count is None: locked_utxo.failure_count = 0 locked_utxo.failure_count += 1 locked_utxo.last_try = timestamp # Commit changes database.db.getSession().commit() database.db.getSession().commit() except Exception as e: LOGGER.error("Failed to process utxo: {} because {}".format(utxo.id, str(e))) database.db.getSession().rollback() sys.exit(1) LOGGER.warn("=== Completed {}".format(PROCESS))
def main(): global LOGGER global CONFIG CONFIG = lib.get_config() LOGGER = lib.get_logger(PROCESS) while True: try: LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB database = lib.get_db() # Get the prebious audit record to find its height previous_audit_record = Pool_audit.getLatest() if previous_audit_record is None: previous_audit_record = Pool_audit() database.db.createDataObj(previous_audit_record) # Create new pool audit record audit_record = Pool_audit() summary_info = wallet.retrieve_summary_info(refresh=True) # Set the height by wallet audit_record.height = int(summary_info["last_confirmed_height"]) # Set pool bock count audit_record.pool_blocks_count = Pool_blocks.count( audit_record.height) - Pool_blocks.count( previous_audit_record.height) # Audit pools liability vs equity audit_record.equity = int( summary_info["amount_currently_spendable"]) + int( summary_info["amount_awaiting_confirmation"]) audit_record.liability = Pool_utxo.get_liability() audit_record.balance = audit_record.equity - audit_record.liability # Add payouts value payments_made = Pool_payment.get_by_height( audit_record.height, audit_record.height - previous_audit_record.height) audit_record.payouts = sum( [payment.amount for payment in payments_made]) # Add payments value pool_credits = Pool_credits.get_by_height( audit_record.height, audit_record.height - previous_audit_record.height) total_credits = 0 if pool_credits is not None: for credit in pool_credits: credits_this_block = sum(credit.credits.values()) total_credits += credits_this_block print("credits_this_block: {}, total_credits: {}".format( credits_this_block, total_credits)) audit_record.payments = total_credits else: audit_record.payments = 0 # Add and Commit the audit record #LOGGER.warn("Create Audit Record: {}".format(json.dumps(audit_record))) database.db.createDataObj(audit_record) LOGGER.warn("=== Completed {}".format(PROCESS)) except Exception as e: lib.teardown_db() LOGGER.exception("Something went wrong: {} ".format( traceback.format_exc())) time.sleep(999)
def main(): global LOGGER LOGGER = lib.get_logger(PROCESS) LOGGER.warn("=== Starting {}".format(PROCESS)) # Connect to DB database = lib.get_db() latest_block = 0 # XXX All in one db transaction.... # Get unlocked blocks from the db unlocked_blocks = Pool_blocks.get_all_unlocked() database.db.getSession().commit() for pb in unlocked_blocks: try: LOGGER.warn("Processing unlocked block: {}".format(pb)) if pb.height > latest_block: latest_block = pb.height # Get Worker_stats of this block + range to calculate reward for each worker worker_shares_window = Worker_shares.get_by_height(pb.height, PPLNS_WINDOW) print("worker_shares_window = {}".format(worker_shares_window)) # Calculate Payment info: if len(worker_shares_window) > 0: # Calcualte reward/share: # XXX TODO: Enhance # What algorithm to use? Maybe: https://slushpool.com/help/manual/rewards # For now, some variation on pplns # Sum up the number of each size share submitted by each user shares_count_map = {} for worker_shares_rec in worker_shares_window: if not worker_shares_rec.worker in shares_count_map: shares_count_map[worker_shares_rec.worker] = {} for pow_size in worker_shares_rec.sizes(): print("pow_size = {}".format(pow_size)) if not pow_size in shares_count_map[worker_shares_rec.worker]: shares_count_map[worker_shares_rec.worker][pow_size] = 0 shares_count_map[worker_shares_rec.worker][pow_size] += worker_shares_rec.num_valid(pow_size) print("Shares Count Map:") pp.pprint(shares_count_map) # Normalize and sum each workers shares to create a "share value" total_value = 0 for worker, worker_shares_count in shares_count_map.items(): print("worker: {}, worker_shares_count: {}".format(worker, worker_shares_count)) sizes = list(worker_shares_count.keys()) print("sizes: {}".format(sizes)) shares_count_map[worker]["value"] = 0 value = 0 for size, count in worker_shares_count.items(): if size == 29: value += float(count) * .33 else: value += float(count) total_value += value shares_count_map[worker]["value"] = value print("Worker {} value: {}".format(worker, value)) # Make payments based on the workers total share_value for worker, worker_shares_count in shares_count_map.items(): worker_rewards = REWARD * worker_shares_count["value"] / total_value # Add or create worker rewards worker_utxo = Pool_utxo.credit_worker(worker, worker_rewards) LOGGER.warn("Credit to user: {} = {}".format(worker, worker_rewards)) # Mark the pool_block state="paid" (maybe "processed" would be more accurate?) pb.state = "paid" database.db.getSession().commit() except Exception as e: database.db.getSession().rollback() LOGGER.error("Something went wrong: {} - {}".format(e, traceback.print_exc())) #database.db.getSession().commit() LOGGER.warn("=== Completed {}".format(PROCESS)) sys.stdout.flush()
def get_tx_slate(user_id, logger, database, method, invoked_by): # 1) Create a Slate slate = None try: locked_utxo = Pool_utxo.get_locked_by_userid(user_id) if locked_utxo is None or locked_utxo.amount < (1 * 1000000000): message = "Insufficient available balance for payout" logger.warn(message) raise PaymentError(400, message) amount = locked_utxo.amount # Generate a slate file try: args = { 'src_acct_name': None, 'amount': int(amount), 'minimum_confirmations': 10, 'max_outputs': 10, 'num_change_outputs': 1, 'selection_strategy_is_use_all': False, 'message': "pool payment: slate: user_id={}".format(user_id), 'target_slate_version': None, 'send_args': None, } logger.warn( "Requesting Payment slate from payment request api: {}".format( args)) slate = wallet.init_send_tx(args) except Exception as e: logger.exception("Failed to get a payment slate: {}".format( str(e))) raise PaymentError(500, str(e)) except PaymentError as e: # My own errors raise except Exception as e: logger.exception("Failed to get a payment slate: {}".format(str(e))) raise PaymentError(500, str(e)) # 2) Create a payment record try: timestamp = datetime.utcnow() payment_record = Pool_payment( user_id=locked_utxo.user_id, timestamp=timestamp, height=slate["height"], address=slate["id"], amount=amount, method=method, fee=slate["fee"], failure_count=locked_utxo.failure_count, state="sent", tx_data=json.dumps(slate), invoked_by=invoked_by, ) database.db.getSession().add(payment_record) # Update the users utxo record locked_utxo.amount = int(slate["fee"]) * -1 locked_utxo.last_try = timestamp locked_utxo.total_amount += amount database.db.getSession().commit() except Exception as e: logger.exception("Failed to create payment record: {}".format(str(e))) raise PaymentError(500, str(e)) # 3) Lock the wallet outputs try: wallet.tx_lock_outputs(slate) except Exception as e: logger.exception("Failed to lock wallet outputs: {}".format(str(e))) raise PaymentError(500, str(e)) # Return the slate return slate
from grinbase.constants.MysqlConstants import MysqlConstants from grinbase.dbaccess import database from grinbase.dbaccess.database import database_details from grinbase.model.pool_utxo import Pool_utxo if __name__ == '__main__': database.db = database_details(MYSQL_CONSTANTS=MysqlConstants()) database.db.initialize() # for i in range(0,10): # tmp = Pool_utxo(id=str(i), address=str(i), amount=1.5*i) # database.db.createDataObj(tmp) utxo = Pool_utxo.getPayable(0)[0] print(utxo) locked_utxo = Pool_utxo.get_locked_by_id(utxo.id) print(locked_utxo) locked_utxo.amount=1.0 database.db.getSession().begin_nested(); locked_utxo.amount=7.0 database.db.getSession().commit() database.db.getSession().commit() utxo = Pool_utxo.getPayable(0)[0] print(utxo) # for utxo in Pool_utxo.getPayable(0): # Pool_utxo.get_locked_by_id(utxo.id)