def run(self): logger.debug('Starting API Status Poller.') global current_api_status_code, current_api_status_response_json db = database.get_connection(read_only=True, integrity_check=False) while self.stop_event.is_set() != True: try: # Check that backend is running, communicable, and caught up with the blockchain. # Check that the database has caught up with SatoshiChaind. if time.time() - self.last_database_check > 10 * 60: # Ten minutes since last check. if not config.FORCE: code = 11 logger.debug('Checking backend state.') check_backend_state() code = 12 logger.debug('Checking database state.') check_database_state(db, backend.getblockcount()) self.last_database_check = time.time() except (BackendError, DatabaseError) as e: exception_name = e.__class__.__name__ exception_text = str(e) logger.debug("API Status Poller: %s", exception_text) jsonrpc_response = jsonrpc.exceptions.JSONRPCServerError(message=exception_name, data=exception_text) current_api_status_code = code current_api_status_response_json = jsonrpc_response.json.encode() else: current_api_status_code = None current_api_status_response_json = None time.sleep(config.BACKEND_POLL_INTERVAL)
def server_db(request): """Enable database access for unit test vectors.""" db = database.get_connection(read_only=False) database.update_version(db) cursor = db.cursor() cursor.execute('''BEGIN''') request.addfinalizer(lambda: cursor.execute('''ROLLBACK''')) return db
def setup_function(function): server.initialise(database_file=tempfile.gettempdir()+'/shellparty.unittest.db', rpc_port=9999, rpc_password='******', backend_password='******', testnet=True, testcoin=False) try: os.remove(config.DATABASE) except: pass # Connect to database. global db db = database.get_connection(read_only=False, foreign_keys=False) from shellpartylib.lib import blocks blocks.initialise(db)
def run_scenario(scenario, rawtransactions_db): """Execute a scenario for integration test, returns a dump of the db, a json with raw transactions and the full log.""" server.initialise(database_file=':memory:', testnet=True, **SCHPARTYD_OPTIONS) config.PREFIX = b'TESTXXXX' util.FIRST_MULTISIG_BLOCK_TESTNET = 1 checkpoints = dict(check.CHECKPOINTS_TESTNET) check.CHECKPOINTS_TESTNET = {} logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger_buff = io.StringIO() handler = logging.StreamHandler(logger_buff) handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(message)s') handler.setFormatter(formatter) logger.addHandler(handler) requests_log = logging.getLogger("requests") requests_log.setLevel(logging.WARNING) asyncio_log = logging.getLogger('asyncio') asyncio_log.setLevel(logging.ERROR) db = database.get_connection(read_only=False) initialise_db(db) raw_transactions = [] for tx in scenario: if tx[0] != 'create_next_block': module = sys.modules['shellpartylib.lib.messages.{}'.format(tx[0])] compose = getattr(module, 'compose') unsigned_tx_hex = transaction.construct(db, compose(db, *tx[1]), **tx[2]) raw_transactions.append({tx[0]: unsigned_tx_hex}) insert_raw_transaction(unsigned_tx_hex, db, rawtransactions_db) else: create_next_block(db, block_index=config.BURN_START + tx[1], parse_block=True) dump = dump_database(db) log = logger_buff.getvalue() db.close() check.CHECKPOINTS_TESTNET = checkpoints return dump, log, json.dumps(raw_transactions, indent=4)
def test_check_database_version(): server.initialise(database_file=tempfile.gettempdir() + '/fixtures.unittest.db', testnet=True, **util_test.SCHPARTYD_OPTIONS) util_test.restore_database(config.DATABASE, CURR_DIR + '/fixtures/scenarios/unittest_fixture.sql') db = database.get_connection(read_only=False) database.update_version(db) version_major, version_minor = database.version(db) assert config.VERSION_MAJOR == version_major assert config.VERSION_MINOR == version_minor check.database_version(db) config.VERSION_MINOR += 1 with pytest.raises(check.DatabaseVersionError) as exception: check.database_version(db) assert exception.value.reparse_block_index == None config.VERSION_MAJOR += 1 with pytest.raises(check.DatabaseVersionError) as exception: check.database_version(db) assert exception.value.reparse_block_index == config.BLOCK_FIRST
def run(self): logger.info('Starting API Server.') db = database.get_connection(read_only=True, integrity_check=False) app = flask.Flask(__name__) auth = HTTPBasicAuth() @auth.get_password def get_pw(username): if username == config.RPC_USER: return config.RPC_PASSWORD return None ###################### #READ API # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): try: return get_rows(db, table=table, **kwargs) except TypeError as e: #TODO: generalise for all API methods raise APIError(str(e)) return get_method for table in API_TABLES: new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=None): if bindings == None: bindings = [] return db_query(db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} methods def generate_create_method(tx): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_args, private_key_wif def create_method(**kwargs): try: transaction_args, common_args, private_key_wif = split_params(**kwargs) return compose_transaction(db, name=tx, params=transaction_args, **common_args) except TypeError as e: #TODO: generalise for all API methods raise APIError(str(e)) return create_method for tx in API_TRANSACTIONS: create_method = generate_create_method(tx) create_method.__name__ = 'create_{}'.format(tx) dispatcher.add_method(create_method) @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise APIError("block_index must be an integer.") cursor = db.cursor() cursor.execute('select * from messages where block_index = ? order by message_index asc', (block_index,)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_index(message_indexes): """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. """ if not isinstance(message_indexes, list): message_indexes = [message_indexes,] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise APIError("All items in message_indexes are not integers") cursor = db.cursor() cursor.execute('SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]),)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_supply(asset): if asset == 'SCH': return backend.get_shell_supply(normalize=False) elif asset == 'SHP': return util.shp_supply(db) else: return util.asset_supply(db, asset) @dispatcher.add_method def get_shp_supply(): logger.warning("Deprecated method: `get_shp_supply`") return util.shp_supply(db) @dispatcher.add_method def get_asset_info(assets): logger.warning("Deprecated method: `get_asset_info`") if not isinstance(assets, list): raise APIError("assets must be a list of asset names, even if it just contains one entry") assetsInfo = [] for asset in assets: # SCH and SHP. if asset in [config.SCH, config.SHP]: if asset == config.SCH: supply = backend.get_shell_supply(normalize=False) else: supply = util.shp_supply(db) assetsInfo.append({ 'asset': asset, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'description': '', 'issuer': None }) continue # User‐created asset. cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: continue #asset not found, most likely else: last_issuance = issuances[-1] locked = False for e in issuances: if e['locked']: locked = True assetsInfo.append({ 'asset': asset, 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': util.asset_supply(db, asset), 'description': last_issuance['description'], 'issuer': last_issuance['issuer']}) return assetsInfo @dispatcher.add_method def get_block_info(block_index): assert isinstance(block_index, int) cursor = db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)) blocks = list(cursor) if len(blocks) == 1: block = blocks[0] elif len(blocks) == 0: raise exceptions.DatabaseError('No blocks found.') else: assert False cursor.close() return block @dispatcher.add_method def get_blocks(block_indexes): """fetches block info and messages for the specified block indexes""" if not isinstance(block_indexes, (list, tuple)): raise APIError("block_indexes must be a list of integers.") if len(block_indexes) >= 250: raise APIError("can only specify up to 250 indexes at a time.") block_indexes_str = ','.join([str(x) for x in block_indexes]) cursor = db.cursor() cursor.execute('SELECT * FROM blocks WHERE block_index IN (%s) ORDER BY block_index ASC' % (block_indexes_str,)) blocks = cursor.fetchall() cursor.execute('SELECT * FROM messages WHERE block_index IN (%s) ORDER BY block_index ASC, message_index ASC' % (block_indexes_str,)) messages = collections.deque(cursor.fetchall()) for block in blocks: # messages_in_block = [] block['_messages'] = [] while len(messages) and messages[0]['block_index'] == block['block_index']: block['_messages'].append(messages.popleft()) assert not len(messages) #should have been cleared out cursor.close() return blocks @dispatcher.add_method def get_running_info(): latestBlockIndex = backend.getblockcount() try: check_database_state(db, latestBlockIndex) except DatabaseError: caught_up = False else: caught_up = True try: cursor = db.cursor() blocks = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (util.CURRENT_BLOCK_INDEX, ))) assert len(blocks) == 1 last_block = blocks[0] cursor.close() except: last_block = None try: last_message = util.last_message(db) except: last_message = None return { 'db_caught_up': caught_up, 'SatoshiChain_block_count': latestBlockIndex, 'last_block': last_block, 'last_message_index': last_message['message_index'] if last_message else -1, 'running_testnet': config.TESTNET, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'version_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION } @dispatcher.add_method def get_element_counts(): counts = {} cursor = db.cursor() for element in ['transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'shellpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'order_expirations', 'bet_expirations', 'order_match_expirations', 'bet_match_expirations', 'messages']: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(): cursor = db.cursor() names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names @dispatcher.add_method def get_holder_count(asset): holders = util.holders(db, asset) addresses = [] for holder in holders: addresses.append(holder['address']) return {asset: len(set(addresses))} @dispatcher.add_method def get_holders(asset): holders = util.holders(db, asset) return holders @dispatcher.add_method def search_raw_transactions(address, unconfirmed=True): return backend.searchrawtransactions(address, unconfirmed=unconfirmed) @dispatcher.add_method def get_unspent_txouts(address, unconfirmed=False): return backend.get_unspent_txouts(address, unconfirmed=unconfirmed, multisig_inputs=False) @dispatcher.add_method def get_tx_info(tx_hex, block_index=None): # block_index mandatory for transactions before block 335000 source, destination, shell_amount, fee, data = blocks.get_tx_info(tx_hex, block_index=block_index) return source, destination, shell_amount, fee, util.hexlify(data) if data else '' @dispatcher.add_method def unpack(data_hex): data = binascii.unhexlify(data_hex) message_type_id = struct.unpack(config.TXTYPE_FORMAT, data[:4])[0] message = data[4:] # TODO: Enabled only for `send`. if message_type_id == send.ID: unpack_method = send.unpack else: raise APIError('unsupported message type') unpacked = unpack_method(db, message, util.CURRENT_BLOCK_INDEX) return message_type_id, unpacked @dispatcher.add_method # TODO: Rename this method. def search_pubkey(pubkeyhash, provided_pubkeys=None): return backend.pubkeyhash_to_pubkey(pubkeyhash, provided_pubkeys=provided_pubkeys) def _set_cors_headers(response): if not config.RPC_NO_ALLOW_CORS: response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' @app.route('/', defaults={'args_path': ''}, methods=['GET', 'POST', 'OPTIONS']) @app.route('/<path:args_path>', methods=['GET', 'POST', 'OPTIONS']) # Only require authentication if RPC_PASSWORD is set. @conditional_decorator(auth.login_required, hasattr(config, 'RPC_PASSWORD')) def handle_root(args_path): """Handle all paths, decide where to forward the query.""" if args_path == '' or args_path.startswith('api/') or args_path.startswith('API/') or \ args_path.startswith('rpc/') or args_path.startswith('RPC/'): if flask.request.method == 'POST': # Need to get those here because it might not be available in this aux function. request_json = flask.request.get_data().decode('utf-8') response = handle_rpc_post(request_json) return response elif flask.request.method == 'OPTIONS': response = handle_rpc_options() return response else: error = 'Invalid method.' return flask.Response(error, 405, mimetype='application/json') elif args_path.startswith('rest/') or args_path.startswith('REST/'): if flask.request.method == 'GET' or flask.request.method == 'POST': # Pass the URL path without /REST/ part and Flask request object. rest_path = args_path.split('/', 1)[1] response = handle_rest(rest_path, flask.request) return response else: error = 'Invalid method.' return flask.Response(error, 405, mimetype='application/json') else: # Not found return flask.Response(None, 404, mimetype='application/json') ###################### # JSON-RPC API ###################### def handle_rpc_options(): response = flask.Response('', 204) _set_cors_headers(response) return response def handle_rpc_post(request_json): """Handle /API/ POST route. Call relevant get_rows/create_transaction wrapper.""" # Check for valid request format. try: request_data = json.loads(request_json) assert 'id' in request_data and request_data['jsonrpc'] == "2.0" and request_data['method'] # params may be omitted except: obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest(data="Invalid JSON-RPC 2.0 request format") return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Only arguments passed as a `dict` are supported. if request_data.get('params', None) and not isinstance(request_data['params'], dict): obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data='Arguments must be passed as a JSON object (list of unnamed arguments not supported)') return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Return an error if the API Status Poller checks fail. if not config.FORCE and current_api_status_code: return flask.Response(current_api_status_response_json, 503, mimetype='application/json') # Answer request normally. # NOTE: `UnboundLocalError: local variable 'output' referenced before assignment` means the method doesn’t return anything. jsonrpc_response = jsonrpc.JSONRPCResponseManager.handle(request_json, dispatcher) response = flask.Response(jsonrpc_response.json.encode(), 200, mimetype='application/json') _set_cors_headers(response) return response ###################### # HTTP REST API ###################### def handle_rest(path_args, flask_request): """Handle /REST/ route. Query the database using get_rows or create transaction using compose_transaction.""" url_action = flask_request.path.split('/')[-1] if url_action == 'compose': compose = True elif url_action == 'get': compose = False else: error = 'Invalid action "%s".' % url_action return flask.Response(error, 400, mimetype='application/json') # Get all arguments passed via URL. url_args = path_args.split('/') try: query_type = url_args.pop(0).lower() except IndexError: error = 'No query_type provided.' return flask.Response(error, 400, mimetype='application/json') # Check if message type or table name are valid. if (compose and query_type not in API_TRANSACTIONS) or \ (not compose and query_type not in API_TABLES): error = 'No such query type in supported queries: "%s".' % query_type return flask.Response(error, 400, mimetype='application/json') # Parse the additional arguments. extra_args = flask_request.args.items() query_data = {} if compose: common_args = {} transaction_args = {} for (key, value) in extra_args: # Determine value type. try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass # Split keys into common and transaction-specific arguments. Discard the privkey. if key in COMMONS_ARGS: common_args[key] = value elif key == 'privkey': pass else: transaction_args[key] = value # Must have some additional transaction arguments. if not len(transaction_args): error = 'No transaction arguments provided.' return flask.Response(error, 400, mimetype='application/json') # Compose the transaction. try: query_data = compose_transaction(db, name=query_type, params=transaction_args, **common_args) except (script.AddressError, exceptions.ComposeError, exceptions.TransactionError, exceptions.BalanceError) as error: error_msg = str(error.__class__.__name__) + ': ' + str(error) return flask.Response(error_msg, 400, mimetype='application/json') else: # Need to de-generate extra_args to pass it through. query_args = dict([item for item in extra_args]) operator = query_args.pop('op', 'AND') # Put the data into specific dictionary format. data_filter = [{'field': key, 'op': '==', 'value': value} for (key, value) in query_args.items()] # Run the query. try: query_data = get_rows(db, table=query_type, filters=data_filter, filterop=operator) except APIError as error: return flask.Response(str(error), 400, mimetype='application/json') # See which encoding to choose from. file_format = flask_request.headers['Accept'] # JSON as default. if file_format == 'application/json' or file_format == '*/*': response_data = json.dumps(query_data) elif file_format == 'application/xml': # Add document root for XML. Note when xmltodict encounters a list, it produces separate tags for every item. # Hence we end up with multiple query_type roots. To combat this we put it in a separate item dict. response_data = serialize_to_xml({query_type: {'item': query_data}}) else: error = 'Invalid file format: "%s".' % file_format return flask.Response(error, 400, mimetype='application/json') response = flask.Response(response_data, 200, mimetype=file_format) return response # Init the HTTP Server. init_api_access_log() http_server = HTTPServer(WSGIContainer(app), xheaders=True) try: http_server.listen(config.RPC_PORT, address=config.RPC_HOST) self.is_ready = True self.ioloop.start() except OSError: raise APIError("Cannot start the API subsystem. Is server already running, or is something else listening on port {}?".format(config.RPC_PORT)) db.close() http_server.stop() self.ioloop.close() return
def reparse(testnet=True): """Reparse all transaction from the database, create a new blockchain and compare it to the old one.""" options = dict(SCHPARTYD_OPTIONS) server.initialise(database_file=':memory:', testnet=testnet, **options) if testnet: config.PREFIX = b'TESTXXXX' logger = logging.getLogger() console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(message)s') console.setFormatter(formatter) logger.addHandler(console) memory_db = database.get_connection(read_only=False) initialise_db(memory_db) data_dir = appdirs.user_data_dir(appauthor=config.SHP_NAME, appname=config.APP_NAME, roaming=True) prod_db_path = os.path.join(data_dir, '{}{}.db'.format(config.APP_NAME, '.testnet' if testnet else '')) prod_db = apsw.Connection(prod_db_path) prod_db.setrowtrace(database.rowtracer) with memory_db.backup("main", prod_db, "main") as backup: backup.step() # Here we don’t use block.reparse() because it reparse db in transaction (`with db`). memory_cursor = memory_db.cursor() for table in blocks.TABLES + ['balances']: memory_cursor.execute('''DROP TABLE IF EXISTS {}'''.format(table)) # Check that all checkpoint blocks are in the database to be tested. if testnet: CHECKPOINTS = check.CHECKPOINTS_TESTNET else: CHECKPOINTS = check.CHECKPOINTS_MAINNET for block_index in CHECKPOINTS.keys(): block_exists = bool(list(memory_cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)))) assert block_exists # Clean consensus hashes if first block hash don’t match with checkpoint. checkpoints = check.CHECKPOINTS_TESTNET if config.TESTNET else check.CHECKPOINTS_MAINNET columns = [column['name'] for column in memory_cursor.execute('''PRAGMA table_info(blocks)''')] for field in ['ledger_hash', 'txlist_hash']: if field in columns: sql = '''SELECT {} FROM blocks WHERE block_index = ?'''.format(field) first_hash = list(memory_cursor.execute(sql, (config.BLOCK_FIRST,)))[0][field] if first_hash != checkpoints[config.BLOCK_FIRST][field]: logger.info('First hash changed. Cleaning {}.'.format(field)) memory_cursor.execute('''UPDATE blocks SET {} = NULL'''.format(field)) blocks.initialise(memory_db) previous_ledger_hash = None previous_txlist_hash = None memory_cursor.execute('''SELECT * FROM blocks ORDER BY block_index''') for block in memory_cursor.fetchall(): try: logger.info('Block (re‐parse): {}'.format(str(block['block_index']))) util.CURRENT_BLOCK_INDEX = block['block_index'] # TODO: Correct?! previous_ledger_hash, previous_txlist_hash = blocks.parse_block(memory_db, block['block_index'], block['block_time'], previous_ledger_hash, block['ledger_hash'], previous_txlist_hash, block['txlist_hash']) except check.ConsensusError as e: message = str(e) if message.find('ledger_hash') != -1: new_ledger = get_block_ledger(memory_db, block['block_index']) old_ledger = get_block_ledger(prod_db, block['block_index']) compare_strings(old_ledger, new_ledger) elif message.find('txlist_hash') != -1: new_txlist = get_block_txlist(memory_db, block['block_index']) old_txlist = get_block_txlist(prod_db, block['block_index']) compare_strings(old_txlist, new_txlist) raise(e)
def initialise(database_file=None, log_file=None, api_log_file=None, testnet=False, testcoin=False, backend_name=None, backend_connect=None, backend_port=None, backend_user=None, backend_password=None, backend_ssl=False, backend_ssl_no_verify=False, backend_poll_interval=None, rpc_host=None, rpc_port=None, rpc_user=None, rpc_password=None, rpc_no_allow_cors=False, force=False, verbose=False, requests_timeout=config.DEFAULT_REQUESTS_TIMEOUT, rpc_batch_size=config.DEFAULT_RPC_BATCH_SIZE, check_asset_conservation=config.DEFAULT_CHECK_ASSET_CONSERVATION, backend_ssl_verify=None, rpc_allow_cors=None): # Data directory data_dir = appdirs.user_data_dir(appauthor=config.SHP_NAME, appname=config.APP_NAME, roaming=True) if not os.path.isdir(data_dir): os.makedirs(data_dir, mode=0o755) # testnet if testnet: config.TESTNET = testnet else: config.TESTNET = False # testcoin if testcoin: config.TESTCOIN = testcoin else: config.TESTCOIN = False network = '' if config.TESTNET: network += '.testnet' if config.TESTCOIN: network += '.testcoin' # Database if database_file: config.DATABASE = database_file else: filename = '{}{}.db'.format(config.APP_NAME, network) config.DATABASE = os.path.join(data_dir, filename) # Log directory log_dir = appdirs.user_log_dir(appauthor=config.SHP_NAME, appname=config.APP_NAME) if not os.path.isdir(log_dir): os.makedirs(log_dir, mode=0o755) # Log if log_file: config.LOG = log_file else: filename = 'server{}.log'.format(network) config.LOG = os.path.join(log_dir, filename) logger.debug('Writing server log to file: `{}`'.format(config.LOG)) if api_log_file: config.API_LOG = api_log_file else: filename = 'server{}.access.log'.format(network) config.API_LOG = os.path.join(log_dir, filename) logger.debug('Writing API accesses log to file: `{}`'.format(config.API_LOG)) # Set up logging. root_logger = logging.getLogger() # Get root logger. log.set_up(root_logger, verbose=verbose, logfile=config.LOG) # Log unhandled errors. def handle_exception(exc_type, exc_value, exc_traceback): logger.error("Unhandled Exception", exc_info=(exc_type, exc_value, exc_traceback)) sys.excepthook = handle_exception ############## # THINGS WE CONNECT TO # Backend name if backend_name: config.BACKEND_NAME = backend_name else: config.BACKEND_NAME = 'addrindex' if config.BACKEND_NAME == 'jmcorgan': config.BACKEND_NAME = 'addrindex' # Backend RPC host (Shellcoin Core) if backend_connect: config.BACKEND_CONNECT = backend_connect else: config.BACKEND_CONNECT = 'localhost' # Backend Core RPC port (Shellcoin Core) if backend_port: config.BACKEND_PORT = backend_port else: if config.TESTNET: if config.BACKEND_NAME == 'shelld': config.BACKEND_PORT = config.DEFAULT_BACKEND_PORT_TESTNET_SCHD else: config.BACKEND_PORT = config.DEFAULT_BACKEND_PORT_TESTNET else: if config.BACKEND_NAME == 'shelld': config.BACKEND_PORT = config.DEFAULT_BACKEND_PORT_SCHD else: config.BACKEND_PORT = config.DEFAULT_BACKEND_PORT try: config.BACKEND_PORT = int(config.BACKEND_PORT) if not (int(config.BACKEND_PORT) > 1 and int(config.BACKEND_PORT) < 65535): raise ConfigurationError('invalid backend API port number') except: raise ConfigurationError("Please specific a valid port number backend-port configuration parameter") # Backend Core RPC user (Shellcoin Core) if backend_user: config.BACKEND_USER = backend_user else: config.BACKEND_USER = '******' # Backend Core RPC password (Shellcoin Core) if backend_password: config.BACKEND_PASSWORD = backend_password else: raise ConfigurationError('backend RPC password not set. (Use configuration file or --backend-password=PASSWORD)') # Backend Core RPC SSL if backend_ssl: config.BACKEND_SSL = backend_ssl else: config.BACKEND_SSL = False # Default to off. # Backend Core RPC SSL Verify if backend_ssl_verify is not None: logger.warning('The server parameter `backend_ssl_verify` is deprecated. Use `backend_ssl_no_verify` instead.') config.BACKEND_SSL_NO_VERIFY = not backend_ssl_verify else: if backend_ssl_no_verify: config.BACKEND_SSL_NO_VERIFY = backend_ssl_no_verify else: config.BACKEND_SSL_NO_VERIFY = False # Default to on (don't support self‐signed certificates) # Backend Poll Interval if backend_poll_interval: config.BACKEND_POLL_INTERVAL = backend_poll_interval else: config.BACKEND_POLL_INTERVAL = 2.0 # Construct backend URL. config.BACKEND_URL = config.BACKEND_USER + ':' + config.BACKEND_PASSWORD + '@' + config.BACKEND_CONNECT + ':' + str(config.BACKEND_PORT) if config.BACKEND_SSL: config.BACKEND_URL = 'https://' + config.BACKEND_URL else: config.BACKEND_URL = 'http://' + config.BACKEND_URL ############## # THINGS WE SERVE # Server API RPC host if rpc_host: config.RPC_HOST = rpc_host else: config.RPC_HOST = 'localhost' # The web root directory for API calls, eg. localhost:14000/rpc/ config.RPC_WEBROOT = '/rpc/' # Server API RPC port if rpc_port: config.RPC_PORT = rpc_port else: if config.TESTNET: if config.TESTCOIN: config.RPC_PORT = config.DEFAULT_RPC_PORT_TESTNET + 1 else: config.RPC_PORT = config.DEFAULT_RPC_PORT_TESTNET else: if config.TESTCOIN: config.RPC_PORT = config.DEFAULT_RPC_PORT + 1 else: config.RPC_PORT = config.DEFAULT_RPC_PORT try: config.RPC_PORT = int(config.RPC_PORT) if not (int(config.RPC_PORT) > 1 and int(config.RPC_PORT) < 65535): raise ConfigurationError('invalid server API port number') except: raise ConfigurationError("Please specific a valid port number rpc-port configuration parameter") # Server API RPC user if rpc_user: config.RPC_USER = rpc_user else: config.RPC_USER = '******' # Server API RPC password if rpc_password: config.RPC_PASSWORD = rpc_password config.RPC = 'http://' + urlencode(config.RPC_USER) + ':' + urlencode(config.RPC_PASSWORD) + '@' + config.RPC_HOST + ':' + str(config.RPC_PORT) + config.RPC_WEBROOT else: config.RPC = 'http://' + config.RPC_HOST + ':' + str(config.RPC_PORT) + config.RPC_WEBROOT # RPC CORS if rpc_allow_cors is not None: logger.warning('The server parameter `rpc_allow_cors` is deprecated. Use `rpc_no_allow_cors` instead.') config.RPC_NO_ALLOW_CORS = not rpc_allow_cors else: if rpc_no_allow_cors: config.RPC_NO_ALLOW_CORS = rpc_no_allow_cors else: config.RPC_NO_ALLOW_CORS = False config.REQUESTS_TIMEOUT = requests_timeout config.RPC_BATCH_SIZE = rpc_batch_size config.CHECK_ASSET_CONSERVATION = check_asset_conservation ############## # OTHER SETTINGS # skip checks if force: config.FORCE = force else: config.FORCE = False # Encoding if config.TESTCOIN: config.PREFIX = b'XX' # 2 bytes (possibly accidentally created) else: config.PREFIX = b'CNTRPRTY' # 8 bytes # (more) Testnet if config.TESTNET: config.MAGIC_BYTES = config.MAGIC_BYTES_TESTNET if config.TESTCOIN: config.ADDRESSVERSION = config.ADDRESSVERSION_TESTNET config.BLOCK_FIRST = config.BLOCK_FIRST_TESTNET_TESTCOIN config.BURN_START = config.BURN_START_TESTNET_TESTCOIN config.BURN_END = config.BURN_END_TESTNET_TESTCOIN config.UNSPENDABLE = config.UNSPENDABLE_TESTNET else: config.ADDRESSVERSION = config.ADDRESSVERSION_TESTNET config.BLOCK_FIRST = config.BLOCK_FIRST_TESTNET config.BURN_START = config.BURN_START_TESTNET config.BURN_END = config.BURN_END_TESTNET config.UNSPENDABLE = config.UNSPENDABLE_TESTNET else: config.MAGIC_BYTES = config.MAGIC_BYTES_MAINNET if config.TESTCOIN: config.ADDRESSVERSION = config.ADDRESSVERSION_MAINNET config.BLOCK_FIRST = config.BLOCK_FIRST_MAINNET_TESTCOIN config.BURN_START = config.BURN_START_MAINNET_TESTCOIN config.BURN_END = config.BURN_END_MAINNET_TESTCOIN config.UNSPENDABLE = config.UNSPENDABLE_MAINNET else: config.ADDRESSVERSION = config.ADDRESSVERSION_MAINNET config.BLOCK_FIRST = config.BLOCK_FIRST_MAINNET config.BURN_START = config.BURN_START_MAINNET config.BURN_END = config.BURN_END_MAINNET config.UNSPENDABLE = config.UNSPENDABLE_MAINNET logger.info('Running v{} of shellparty-lib.'.format(config.VERSION_STRING)) if config.FORCE: logger.warning('THE OPTION `--force` IS NOT FOR USE ON PRODUCTION SYSTEMS.') # Lock if not config.FORCE: get_lock() # Database logger.info('Connecting to database.') db = database.get_connection(read_only=False) util.CURRENT_BLOCK_INDEX = blocks.last_db_index(db) return db