def _log_to_logger(*args, **kwargs): start_time = int(round(time.time() * 1000)) request_time = datetime.now() # Log information about the request before it is processed for debugging purposes REQUESTS_LOG.info('%s | %s | %s | %s' % (request_time, request.remote_addr, request.method, request.url)) if request.headers is not None: for key, value in request.headers.items(): REQUESTS_LOG.info(' HEADERS | %s: %s' % (key, value)) if request.json is not None: for key, value in request.json.items(): REQUESTS_LOG.info(' BODY | %s: %s' % (key, value)) actual_response = response try: actual_response = fn(*args, **kwargs) except Exception as ex: response_status = '500 ' + str(ex) LOG.error('%s caused an exception: %s' % (request.url, ex)) error_traceback = traceback.format_exc() for line in error_traceback.split('\n'): LOG.error(line) if get_mail_on_exception() is True: variables = {'HOST': get_host(), 'TRACEBACK': error_traceback} body_template = os.path.join('server_exception') sendmail(recipients=get_notification_email(), subject='Exception occurred @ %s' % get_host(), body_template=body_template, variables=variables) else: response_status = response.status end_time = int(round(time.time() * 1000)) REQUESTS_LOG.info('%s | %s | %s | %s | %s | %s ms' % (request_time, request.remote_addr, request.method, request.url, response_status, end_time - start_time)) return actual_response
trigger_type = 'HTTPDeleteRequest' script = 'Echo.py' # ------------------------------------------------------------------------------------------------- print('Creating HTTP DELETE request trigger') response = spellbook_call('save_trigger', trigger_id, '-t=%s' % trigger_type, '-sc=%s' % script, '-st=Active', '--multi') assert response is None print('Checking if trigger has not been triggered yet') response = spellbook_call('get_trigger_config', trigger_id) assert response['triggered'] is 0 assert response['trigger_type'] == trigger_type print('Activating HTTP DELETE request trigger with data') host, port = get_host(), get_port() url = 'http://{host}:{port}/spellbook/triggers/{trigger_id}/delete'.format(host=host, port=port, trigger_id=trigger_id) headers = {} data = {'test': 'hello'} try: r = requests.delete(url, headers=headers, json=data) print(r.text) assert simplejson.loads(r.text) == data except Exception as ex: print('DELETE %s failed: %s' % (url, ex), file=sys.stderr) sys.exit(1) print('Checking if trigger has been triggered') response = spellbook_call('get_trigger_config', trigger_id)
import sys import requests from helpers.configurationhelpers import get_host, get_port url = 'http://{host}:{port}/spellbook/triggers/Notary-request/post'.format( host=get_host(), port=get_port()) data = {'message': 'This is a test message2'} print('Making new Notary request') try: r = requests.post(url, json=data) print(r.text) except Exception as ex: print('POST %s failed: %s' % (url, ex), file=sys.stderr) sys.exit(1)
def __init__(self): super(SpellbookRESTAPI, self).__init__() # Initialize variables self.host = get_host() self.port = get_port() # Log the requests to the REST API in a separate file by installing a custom LoggingPlugin self.install(self.log_to_logger) # Make sure that an api_keys.json file is present, the first time the server is started # a new random api key and secret pair will be generated if not os.path.isfile('json/private/api_keys.json'): LOG.info('Generating new API keys') initialize_api_keys_file() LOG.info('Starting Bitcoin Spellbook') try: get_hot_wallet() except Exception as ex: LOG.error('Unable to decrypt hot wallet: %s' % ex) sys.exit(1) LOG.info( 'To make the server run in the background: use Control-Z, then use command: bg %1' ) # Initialize the routes for the REST API self.route( '/', method='GET', callback=self.index ) # on linux this gets requested every minute or so, but not on windows self.route('/favicon.ico', method='GET', callback=self.get_favicon) # Route for ping, to test if server is online self.route('/spellbook/ping', method='GET', callback=self.ping) # Routes for managing blockexplorers self.route('/spellbook/explorers', method='GET', callback=self.get_explorers) self.route('/spellbook/explorers/<explorer_id:re:[a-zA-Z0-9_\-.]+>', method='POST', callback=self.save_explorer) self.route('/spellbook/explorers/<explorer_id:re:[a-zA-Z0-9_\-.]+>', method='GET', callback=self.get_explorer_config) self.route('/spellbook/explorers/<explorer_id:re:[a-zA-Z0-9_\-.]+>', method='DELETE', callback=self.delete_explorer) # Routes for retrieving data from the blockchain self.route('/spellbook/blocks/latest', method='GET', callback=self.get_latest_block) self.route('/spellbook/blocks/<height:int>', method='GET', callback=self.get_block_by_height) self.route('/spellbook/blocks/<block_hash:re:[a-f0-9]+>', method='GET', callback=self.get_block_by_hash) self.route('/spellbook/transactions/<txid:re:[a-f0-9]+>/prime_input', method='GET', callback=self.get_prime_input_address) self.route('/spellbook/transactions/<txid:re:[a-f0-9]+>', method='GET', callback=self.get_transaction) self.route( '/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/transactions', method='GET', callback=self.get_transactions) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/balance', method='GET', callback=self.get_balance) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/utxos', method='GET', callback=self.get_utxos) # Routes for Simplified Inputs List (SIL) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/SIL', method='GET', callback=self.get_sil) # Routes for Profile self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/profile', method='GET', callback=self.get_profile) # Routes for Simplified UTXO List (SUL) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/SUL', method='GET', callback=self.get_sul) # Routes for Linked Lists self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LAL', method='GET', callback=self.get_lal) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LBL', method='GET', callback=self.get_lbl) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LRL', method='GET', callback=self.get_lrl) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LSL', method='GET', callback=self.get_lsl) # Routes for Random Address self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/SIL', method='GET', callback=self.get_random_address_from_sil) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/LBL', method='GET', callback=self.get_random_address_from_lbl) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/LRL', method='GET', callback=self.get_random_address_from_lrl) self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/LSL', method='GET', callback=self.get_random_address_from_lsl) # Routes for Triggers self.route('/spellbook/triggers', method='GET', callback=self.get_triggers) self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='GET', callback=self.get_trigger) self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='POST', callback=self.save_trigger) self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='DELETE', callback=self.delete_trigger) self.route( '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/activate', method='GET', callback=self.activate_trigger) self.route( '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/message', method='POST', callback=self.verify_signed_message) self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/get', method='GET', callback=self.http_get_request) self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/post', method='POST', callback=self.http_post_request) self.route( '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/delete', method='DELETE', callback=self.http_delete_request) self.route( '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/check', method='GET', callback=self.check_trigger) self.route('/spellbook/check_triggers', method='GET', callback=self.check_all_triggers) # Additional routes for Rest API endpoints self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='GET', callback=self.http_get_request) self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='OPTIONS', callback=self.http_get_request) self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='POST', callback=self.http_post_request) self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='DELETE', callback=self.http_delete_request) self.route('/html/<trigger_id:re:[a-zA-Z0-9_\-.]+>', method='GET', callback=self.html_request) self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>/message', method='POST', callback=self.verify_signed_message) self.route('/api/sign_message', method='POST', callback=self.sign_message) # Routes for QR image generation self.route('/api/qr', method='GET', callback=self.qr) # Routes for Actions self.route('/spellbook/actions', method='GET', callback=self.get_actions) self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>', method='GET', callback=self.get_action) self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>', method='POST', callback=self.save_action) self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>', method='DELETE', callback=self.delete_action) self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>/run', method='GET', callback=self.run_action) # Routes for retrieving log messages self.route('/spellbook/logs/<filter_string>', method='GET', callback=self.get_logs) # Routes for RevealSecret actions self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>/reveal', method='GET', callback=self.get_reveal) # Check if there are explorers configured, this will also initialize the default explorers on first startup if len(get_explorers()) == 0: LOG.warning('No block explorers configured!') try: # start the webserver for the REST API if get_enable_ssl() is True: self.run(host=self.host, port=self.port, debug=False, server='sslwebserver') else: self.run(host=self.host, port=self.port, debug=True, server='cheroot') except Exception as ex: LOG.error('An exception occurred in the main loop: %s' % ex) error_traceback = traceback.format_exc() for line in error_traceback.split('\n'): LOG.error(line) if get_mail_on_exception() is True: variables = {'HOST': get_host(), 'TRACEBACK': error_traceback} body_template = os.path.join('server_exception') sendmail(recipients=get_notification_email(), subject='Main loop Exception occurred @ %s' % get_host(), body_template=body_template, variables=variables)
print('Setting up Voucher') print('----------------------------------------------\n') # -------------------------------------------------------------------------------------------------------- # Clean up old triggers and actions first # -------------------------------------------------------------------------------------------------------- clean_up_triggers(trigger_ids=['RedeemVoucher']) # -------------------------------------------------------------------------------------------------------- # Create Triggers # -------------------------------------------------------------------------------------------------------- print('\nCreating Trigger...') trigger_id = 'RedeemVoucher' trigger_type = TriggerType.HTTPPOSTREQUEST script = os.path.join('Voucher', 'RedeemVoucher.py') response = spellbook_call('save_trigger', trigger_id, '--type=%s' % trigger_type, '--script=%s' % script, '--multi', '--status=Active') assert response is None print('HTTP POST endpoint created') print('To create a new Voucher request, send a HTTP POST request with the voucher code as the "voucher" field ' \ 'in the request data and the bitcoin address as the "address" field to:') url = 'http://{host}:{port}/api/RedeemVoucher'.format(host=get_host(), port=get_port()) print(url)
assert response is None trigger_id = 'PaymentProcessorTransactionReceived' trigger_type = 'HTTPPostRequest' script = os.path.join('PaymentProcessor', 'PaymentProcessorTransactionReceived.py') response = spellbook_call('save_trigger', trigger_id, '-t=%s' % trigger_type, '--multi', '-sc=%s' % script) assert response is None # -------------------------------------------------------------------------------------------------------- print('\n\n') print('HTTP POST endpoint created') print('To create a new Payment request, send a HTTP POST request with the following fields: seller_id, amount_fiat, currency') print('To this url:') url = 'http://{host}:{port}/spellbook/triggers/PaymentProcessorNewPayment/post'.format(host=get_host(), port=get_port()) print(url) # -------------------------------------------------------------------------------------------------------- print('\n\n') print('HTTP GET endpoint created') print('To get a payment status, send a HTTP GET request with the following fields: payment_request_id') print('To this url:') url = 'http://{host}:{port}/spellbook/triggers/PaymentProcessorPaymentStatus/get'.format(host=get_host(), port=get_port()) print(url)
def uptime_check(email, ipfs=False, reboot=False, ssl=None): LOG.info('CPU: %s%%' % psutil.cpu_percent()) LOG.info('RAM: %s' % str(psutil.virtual_memory())) LOG.info('Checking if spellbook server is still online') if ssl is None: url = 'http://{host}:{port}/spellbook/ping'.format(host=get_host(), port=get_port()) else: url = 'https://{host}:{port}/spellbook/ping'.format(host=ssl, port=get_port()) try: r = requests.get(url=url, timeout=10) response = r.json() except Exception as ex: LOG.error('Unable to ping spellbook server: %s' % ex) response = {} online = True if 'success' in response and response[ 'success'] is True else False if not online: LOG.error('Spellbook server is not online!') if email is not None: variables = { 'HOST': get_host(), 'SPELLBOOK_LOG': get_recent_spellbook_log(), 'REQUESTS_LOG': get_recent_requests_log() } body_template = os.path.join('server_offline') success = sendmail(recipients=email, subject='Spellbookserver @ %s is offline!' % get_host(), body_template=body_template, variables=variables) if success is True: LOG.info('Email sent successfully') if reboot is True and platform.system() == 'Linux': LOG.info('Rebooting server because uptime check failed!') RunCommandProcess(command='sudo reboot').run() else: LOG.error('Email to %s failed!' % email) else: LOG.info('Server is online') if ipfs is True: try: response = add_str('ping') except Exception as ex: LOG.error('IPFS node is offline: %s' % ex) if email is not None: variables = {'HOST': get_host()} body_template = os.path.join('ipfs_offline') success = sendmail(recipients=email, subject='IPFS node @ %s is offline!' % get_host(), body_template=body_template, variables=variables) if success is True: LOG.info('Email sent successfully') if reboot is True and platform.system() == 'Linux': LOG.info( 'Rebooting server because ipfs node is offline!') RunCommandProcess(command='sudo reboot').run() else: LOG.error('Email to %s failed!' % email)
def run(self): LOG.info('Running Spellbook Script: %s' % os.path.splitext(os.path.basename(__file__))[0]) if self.json is not None: if 'seller_id' not in self.json: LOG.error( 'Payment request json does not contain the seller id') return if 'amount_fiat' not in self.json: LOG.error( 'Payment request json does not contain the fiat amount!') return if 'currency' not in self.json: LOG.error( 'Payment request json does not contain the fiat currency!') return elif self.json['currency'] not in ['EUR', 'USD']: LOG.error( 'Payment processor currently only supports EUR or USD as currency!' ) return # Create a new payment request payment_request = PaymentRequest() payment_request.seller_id = self.json['seller_id'] payment_request.amount_fiat = self.json['amount_fiat'] payment_request.currency = self.json['currency'] payment_request.note = self.json[ 'note'] if 'note' in self.json else None # Use the number of times the trigger has been triggered as the index in the hot wallet account payment_request.address = get_address_from_wallet( account=ACCOUNT, index=self.triggered) # Get the current BTC price from bitcoinaverage url = 'https://apiv2.bitcoinaverage.com/indices/global/ticker/BTC{currency}'.format( currency=payment_request.currency) LOG.info('Retrieving BTC%s price from bitcoinaverage.com' % payment_request.currency) LOG.info('GET %s' % url) try: r = requests.get(url=url) price_data = r.json() except Exception as ex: LOG.error( 'Unable to retrieve BTC price from bitcoinaverage.com: %s' % ex) self.http_response = { 'error': 'Unable to convert %s amount to BTC amount' % payment_request.currency } return payment_request.price_btc = price_data['last'] payment_request.price_timestamp = price_data['timestamp'] if payment_request.price_btc == 0: LOG.error('BTC price can not be 0!') self.http_response = { 'error': 'Unable to convert %s amount to BTC amount' % payment_request.currency } return payment_request.amount_btc = int(payment_request.amount_fiat / payment_request.price_btc * 1e8) LOG.info('Created new payment request: %s' % payment_request.payment_request_id) LOG.info('Fiat Amount: %s %s' % (payment_request.amount_fiat, payment_request.currency)) LOG.info('BTC Amount: %s (price %s %s/BTC @ %s)' % (payment_request.amount_btc, payment_request.price_btc, payment_request.currency, datetime.fromtimestamp(payment_request.price_timestamp ).strftime('%Y-%m-%d %H:%M:%S'))) LOG.info('Note: %s' % payment_request.note) LOG.info('Address: %s' % payment_request.address) payment_request.save() # Set the HTTP response with the payment request details self.http_response = payment_request.json_encodable() # Create a trigger to monitor the balance of the address (This is a fallback in case the listener doesn't pick up the transaction) # The script will then check the number of confirmations of the transaction if one is received trigger = get_trigger( trigger_id=payment_request.payment_request_id, trigger_type=TriggerType.BALANCE) trigger.address = payment_request.address trigger.amount = payment_request.amount_btc trigger.script = os.path.join('PaymentProcessor', 'PaymentProcessorPaymentStatus.py') trigger.data = { 'payment_request_id': payment_request.payment_request_id } trigger.self_destruct = int(time.time()) + REQUEST_TIMEOUT trigger.status = 'Active' trigger.multi = True trigger.save() # Spawn up a separate process to listen for the payment transaction url = 'http://%s:%s/spellbook/triggers/PaymentProcessorTransactionReceived/post' % ( get_host(), get_port()) notify_program = os.path.join('helpers', 'notify_transaction.py') command = r'%s %s %s #txid#' % (notify_program, url, payment_request.payment_request_id) # Construct the command for the listener so that it listens for any receiving transactions on the address and executes the notify_transaction program when # a transaction is detected and stop the listener if no tx happens within the timeout period. listener_program = os.path.join('listeners', 'transaction_listener.py') run_command = r'%s --address=%s --timeout=%s --exit --receive --command="%s"' % ( listener_program, payment_request.address, LISTENER_TIMEOUT, command) # If we are configured for testnet we must also add the --testnet flag to the listener if get_use_testnet(): run_command += ' --testnet' action = get_action(action_id='start_listener', action_type=ActionType.SPAWNPROCESS) action.run_command = run_command # Run the action immediately instead of saving it, so we are not creating new actions with each request action.run()