Esempio n. 1
0
        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)
Esempio n. 3
0
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)
Esempio n. 4
0
    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)
Esempio n. 5
0
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)
Esempio n. 7
0
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)
Esempio n. 8
0
    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()