from typing import Optional, Union from urllib.parse import urlparse from fastapi import FastAPI, Request from fastapi.responses import JSONResponse import time, socket from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import QueuePool import uvicorn #local from rpcs import RPCHost, RPCXMR from models import Return from meta import Queue from functions import checksumCheck, validDns import config class ErrorException(Exception): def __init__(self, code: str, status: str, status_message: str): self.status = status self.code = code self.status_message = status_message app = FastAPI() @app.get("/") def root(): return @app.exception_handler(ErrorException) def unicorn_exception_handler(request: Request, exc: ErrorException): return JSONResponse( status_code=exc.code, content={ "status": f"{exc.status}", "message": f"{exc.status_message}" }, ) @app.get("/api/receive") def receive(method: str, address: str, callback: Union[str, None] = None): if not method: raise ErrorException(code=422,status="error",status_message='Empty method used') if not address: raise ErrorException(code=422,status="error",status_message='Empty destination address') if method.lower() not in ['btc', 'btct', 'ltc', 'doge', 'zec', 'bch', 'xmr']: raise ErrorException(code=422,status="error",status_message='Invalid method used') if checksumCheck(method.lower(), address) == False: raise ErrorException(code=422,status="error",status_message='Invalid Destination Address') if callback: try: data = urlparse(callback) #scheme validation if data.scheme == 'http' or data.scheme == 'https': #domain validation if validDns(data.netloc) != True: raise ErrorException(code=422,status="error",status_message='Invalid callback: domain name does not resolve') else: raise ErrorException(code=422,status="error",status_message='Invalid callback: wrong url scheme, we accept http or https only') except: callback = 'None' callback_req_n = 0 else: callback = 'None' callback_req_n = 1 ## RPC connection to Demons match method.upper(): case 'BTC' | 'LTC' | 'ZEC' | 'BCH' | 'BTCT' | 'DOGE': rpc = RPCHost("http://%s:%s@%s:%s" % (config.rpcs[method.upper()]['user'], config.rpcs[method.upper()]['pass'], config.rpcs[method.upper()]['host'], config.rpcs[method.upper()]['port'])) case 'XMR': rpc = RPCXMR(config.rpcs[method.upper()]['host'], config.rpcs[method.upper()]['user'], config.rpcs[method.upper()]['pass']) case _: raise ErrorException(code=422,status="error",status_message='RPC undergoing maintenance') ## checking if wallet is loaded # wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,True,True,False) try: match method.upper(): case 'XMR': wallet = rpc.call('create_address',{"account_index": 0}) wallet = wallet['address'] case 'LTC' | 'ZEC' | 'DOGE': #wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,False,True) wallet = rpc.call('getnewaddress') case 'BTC' | 'BTCT': #wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,True,True,False) wallet = rpc.call('getnewaddress','', 'p2sh-segwit') case 'BCH': wallet = rpc.call('getnewaddress') wallet = wallet.replace('bitcoincash:', '') case _: ## notify admin about the error raise ErrorException(code=422, status="error", status_message='RPC unavailable') except Exception as error: ## notify admin about the error # Failed to connect for remote procedure call. raise ErrorException(code=422, status="error", status_message='Failed to connect for remote procedure call.') ## SQL Connection with SQLAlchemy engine = create_engine( "mysql+pymysql://%s:%s@%s:%s/dev" % (config.DB['user'], config.DB['pass'], config.DB['host'], config.DB['port']), poolclass=QueuePool, #echo=True, #debug sqlalchemy pool_size=4, max_overflow=0, pool_timeout=15, # Wait 15s for a connection ) Session = sessionmaker(bind = engine) session = Session() if wallet: try: q = Queue(txhash = 'None', time = int(time.time()), account = method.upper(), fee = config.fee['REGULAR'][method.upper()], ready = 0, confirmations =0, callbackurl = callback, generated_address = wallet, destination = address, balance_received = '0.00000000', callback_req = callback_req_n, ip = socket.gethostbyname(socket.gethostname()) , hostname = socket.gethostname(), merchantId = 'None', dateTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) session.add(q) session.commit() session.close() except Exception as error: ## notify admin about the error raise ErrorException(code=422,status="error",status_message='Invalid response from dbServer') else: ## notify admin about the error raise ErrorException(code=422,status="error",status_message='Invalid response from rpcServer') return Return( address = wallet, destination = address, callback_url = callback, fee = config.fee['REGULAR'][method.upper()], status = 'success' ) # Run if __name__ == '__main__': uvicorn.run('main:app', host='0.0.0.0') #workers=4 (doesn't work with reload)