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