Пример #1
0
    def render_POST(self, request):
        """ POST request for /profiler/
            We expect 'start' or 'stop' as request args and, in the case of stop, also an optional parameter 'filepath'
            'start': bool to represent it should start the profiler
            'stop': bool to represent it should stop the profiler
            'filepath': str of the file path where to save the profiler file

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        data_read = request.content.read()
        post_data = json.loads(data_read.decode('utf-8')) if data_read else {}
        ret = {'success': True}

        if 'start' in post_data:
            self.manager.start_profiler()

        elif 'stop' in post_data:
            if 'filepath' in post_data:
                filepath = post_data['filepath']
            else:
                filepath = self.gen_dump_filename()

            self.manager.stop_profiler(save_to=filepath)
            ret['saved_to'] = filepath

        else:
            ret['success'] = False

        return json.dumps(ret, indent=4).encode('utf-8')
Пример #2
0
    def render_GET(self, request):
        """ GET request for /wallet/history/
            Expects 'page' and 'count' as request args
            'page' is the pagination number
            'count' is the number of elements in each page

            Returns a history array (can be 'SpentTx' or 'UnspentTx') and the total number of pages

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        page = int(request.args[b'page'][0])
        count = int(request.args[b'count'][0])

        history_tuple, total = self.manager.wallet.get_history(count, page)

        history = []
        for obj in history_tuple:
            history_dict = obj.to_dict()
            history_dict['tx_id'] = history_dict['tx_id']
            if 'from_tx_id' in history_dict:
                history_dict['from_tx_id'] = history_dict['from_tx_id']
            history.append(history_dict)

        data = {'history': history, 'total_pages': math.ceil(total / count)}
        return json.dumps(data, indent=4).encode('utf-8')
Пример #3
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_history/
            Expects 'addresses[]' as request args
            'addresses[]' is an array of address

            Returns an array of WalletIndex for each address

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        wallet_index = self.manager.tx_storage.wallet_index

        if not wallet_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        addresses = request.args[b'addresses[]']

        history = []
        seen: Set[bytes] = set()
        for address_to_decode in addresses:
            address = address_to_decode.decode('utf-8')
            for tx_hash in wallet_index.get_from_address(address):
                tx = self.manager.tx_storage.get_transaction(tx_hash)
                if tx_hash not in seen:
                    seen.add(tx_hash)
                    history.append(tx.to_json_extended())

        data = {'history': history}
        return json.dumps(data, indent=4).encode('utf-8')
Пример #4
0
    def _render_GET_thread(self, request: Request) -> bytes:
        """ GET request /graphviz/full.{format}
            Returns the rendered graph file
        """
        set_cors(request, 'GET')

        tx_storage = self.manager.tx_storage

        graphviz = GraphvizVisualizer(tx_storage)
        if b'weight' in request.args:
            graphviz.show_weight = self.parse_bool_arg(
                request.args[b'weight'][0].decode('utf-8'))
        if b'acc_weight' in request.args:
            graphviz.show_acc_weight = self.parse_bool_arg(
                request.args[b'acc_weight'][0].decode('utf-8'))
        if b'verifications' in request.args:
            graphviz.include_verifications = self.parse_bool_arg(
                request.args[b'verifications'][0].decode('utf-8'))
        if b'funds' in request.args:
            graphviz.include_funds = self.parse_bool_arg(
                request.args[b'funds'][0].decode('utf-8'))
        if b'only_blocks' in request.args:
            graphviz.only_blocks = self.parse_bool_arg(
                request.args[b'only_blocks'][0].decode('utf-8'))
        dot = graphviz.dot(format=self.format.dot)

        request.setHeader(b'content-type', self.format.content_type)
        if self.format is FileFormat.DOT:
            return str(dot).encode('utf-8')
        return dot.pipe()
Пример #5
0
    def _render_GET_thread(self, request: Request) -> bytes:
        """ GET request /graphviz/neighbours.{format}
            Returns the rendered graph file
        """
        set_cors(request, 'GET')

        tx_storage = self.manager.tx_storage

        tx_hex = request.args[b'tx'][0].decode('utf-8')
        success, message = validate_tx_hash(tx_hex, tx_storage)
        if not success:
            return json.dumps({
                'success': False,
                'message': message
            }, indent=4).encode('utf-8')

        graph_type = request.args[b'graph_type'][0].decode('utf-8')
        max_level = min(int(request.args[b'max_level'][0]),
                        settings.MAX_GRAPH_LEVEL)
        tx = tx_storage.get_transaction(bytes.fromhex(tx_hex))

        graphviz = GraphvizVisualizer(tx_storage)
        dot = graphviz.tx_neighborhood(tx,
                                       format=self.format.dot,
                                       max_level=max_level,
                                       graph_type=graph_type)

        request.setHeader(b'content-type', self.format.content_type)
        if self.format is FileFormat.DOT:
            return str(dot).encode('utf-8')
        return dot.pipe()
Пример #6
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token/

            Expects 'id' (hash) as GET parameter of the queried token

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}).encode('utf-8')

        if b'id' in request.args:
            try:
                token_uid_str = request.args[b'id'][0].decode('utf-8')
                token_uid = bytes.fromhex(token_uid_str)
            except (ValueError, AttributeError):
                return json.dumps({
                    'success': False,
                    'message': 'Invalid token id'
                }).encode('utf-8')

            data = self.get_one_token_data(token_uid)
        else:
            data = self.get_list_token_data()

        return json.dumps(data).encode('utf-8')
Пример #7
0
    def render_GET(self, request):
        """ Get request /decode_tx/ that returns the tx decoded, if success

            Expects 'hex_tx' as GET parameter

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        parsed = parse_get_arguments(request.args, ARGS)
        if not parsed['success']:
            return get_missing_params_msg(parsed['missing'])

        try:
            tx_bytes = bytes.fromhex(parsed['args']['hex_tx'])
            tx = tx_or_block_from_bytes(tx_bytes)
            tx.storage = self.manager.tx_storage
            data = get_tx_extra_data(tx)
        except ValueError:
            data = {'success': False, 'message': 'Invalid hexadecimal data'}
        except struct.error:
            data = {'success': False, 'message': 'Could not decode transaction'}

        return json.dumps(data, indent=4).encode('utf-8')
Пример #8
0
    def render_GET(self, request):
        """ Get request to /tips/ that return a list of tips hashes

            'timestamp' is an optional parameter to be used in the get_tx_tips method

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        timestamp = None
        if b'timestamp' in request.args:
            try:
                timestamp = int(request.args[b'timestamp'][0])
            except ValueError:
                return json.dumps({
                    'success':
                    False,
                    'message':
                    'Invalid timestamp parameter, expecting an integer'
                }).encode('utf-8')

        tx_tips = self.manager.tx_storage.get_tx_tips(timestamp)
        ret = {'success': True, 'tips': [tip.data.hex() for tip in tx_tips]}
        return json.dumps(ret).encode('utf-8')
Пример #9
0
    def render_GET(self, request):
        """ GET request for /get_block_template/
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        # params
        raw_address = request.args.get(b'address')
        if raw_address:
            address = decode_address(raw_address[0].decode())
        else:
            address = b''
        caps = set(map(lambda s: Capabilities(s.decode()), request.args.get(b'capabilities', [])))
        merged_mining = Capabilities.MERGED_MINING in caps

        if not self.manager.can_start_mining():
            self.log.debug('cannot generate Block Template, node syncing')
            # XXX: HTTP 503 Service Unavailable is suitable for temporary server errors
            raise APIError('Node syncing', 503)

        # get block
        # XXX: miner can edit block data and output_script, so it's fine if address is None
        block = self.manager.generate_mining_block(address=address, merge_mined=merged_mining)

        # serialize
        data = block.to_json(include_metadata=True)
        data.pop('hash')
        data.pop('inputs')
        data.pop('nonce', None)
        data.pop('aux_pow', None)

        return json_dumpb(data)
Пример #10
0
    def render_GET(self, request):
        """ Get request to /dashboard-tx/ that return a list of blocks and tx
            We expect two GET parameters: 'block' and 'tx'

            'block': int that indicates de quantity of blocks I should return
            'tx': int that indicates de quantity of tx I should return

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        # Get quantity for each
        block_count = int(request.args[b'block'][0])
        tx_count = int(request.args[b'tx'][0])

        # Restrict counts
        block_count = min(block_count, settings.MAX_DASHBOARD_COUNT)
        tx_count = min(tx_count, settings.MAX_DASHBOARD_COUNT)

        transactions, _ = self.manager.tx_storage.get_newest_txs(
            count=tx_count)
        serialized_tx = [tx.to_json_extended() for tx in transactions]

        blocks, _ = self.manager.tx_storage.get_newest_blocks(
            count=block_count)
        serialized_blocks = [block.to_json_extended() for block in blocks]

        data = {
            'transactions': serialized_tx,
            'blocks': serialized_blocks,
        }

        return json.dumps(data, indent=4).encode('utf-8')
Пример #11
0
    def render_GET(self, request):
        """ GET request /getmininginfo/

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.can_start_mining():
            return json.dumps({
                'success': False,
                'message': 'Node still syncing'
            }).encode('utf-8')

        # We can use any address.
        burn_address = bytes.fromhex(
            settings.P2PKH_VERSION_BYTE.hex() +
            'acbfb94571417423c1ed66f706730c4aea516ac5762cccb8')
        block = self.manager.generate_mining_block(address=burn_address)

        height = block.calculate_height() - 1
        difficulty = diff_from_weight(block.weight)

        parent = block.get_block_parent()
        hashrate = 2**(parent.weight - log(30, 2))

        data = {
            'hashrate': hashrate,
            'difficulty': difficulty,
            'blocks': height,
            'success': True,
        }
        return json.dumps(data, indent=4).encode('utf-8')
Пример #12
0
    def render_GET(self, request):
        """ Get request /decode_tx/ that returns the tx decoded, if success

            Expects 'hex_tx' as GET parameter

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if b'hex_tx' in request.args:
            requested_decode = request.args[b'hex_tx'][0].decode('utf-8')
        else:
            return get_missing_params_msg('hex_tx')

        pattern = r'[a-fA-F\d]+'
        if re.match(pattern,
                    requested_decode) and len(requested_decode) % 2 == 0:
            tx_bytes = bytes.fromhex(requested_decode)

            try:
                tx = tx_or_block_from_bytes(tx_bytes)
                tx.storage = self.manager.tx_storage
                data = get_tx_extra_data(tx)
            except struct.error:
                data = {'success': False}

        else:
            data = {'success': False}
        return json.dumps(data, indent=4).encode('utf-8')
Пример #13
0
    def render_GET(self, request):
        """ GET request for /wallet/address/
            Expects a parameter 'new' (boolean) that says if we should create a new address
            Returns the address (new or old)

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if b'new' in request.args:
            new = request.args[b'new'][0].decode('utf-8') == 'true'
        else:
            new = False

        if new:
            # When user clicks 'Generate new address' we have to mark the old one
            # as used and return a new one but not mark the new as used
            # Because if the user refreshs the page we need to show the same
            self.manager.wallet.get_unused_address(mark_as_used=True)

        address = self.manager.wallet.get_unused_address(mark_as_used=False)

        data = {
            'address': address,
        }
        return json.dumps(data, indent=4).encode('utf-8')
Пример #14
0
    def render_POST(self, request: Request) -> bytes:
        """ POST request for /thin_wallet/address_history/

            It has the same behaviour as the GET request but when using the GET
            we have a limit of addresses to put as query param, otherwise we end up
            reaching the HTTP content length limit
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        if not self.manager.tx_storage.indexes.addresses:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        raw_body_bytes = request.content.read() or b''
        raw_body_str = raw_body_bytes.decode('utf-8')
        try:
            post_data = json.loads(raw_body_str)
        except json.JSONDecodeError:
            return get_missing_params_msg('invalid json')

        if 'addresses' not in post_data:
            return get_missing_params_msg('addresses')

        return self.get_address_history(post_data.get('addresses'),
                                        post_data.get('hash'))
Пример #15
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token/

            Expects 'id' (hash) as GET parameter of the queried token

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.tx_storage.tokens_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}).encode('utf-8')

        if b'id' not in request.args:
            return get_missing_params_msg('id')

        try:
            token_uid_str = request.args[b'id'][0].decode('utf-8')
            token_uid = bytes.fromhex(token_uid_str)
        except (ValueError, AttributeError):
            return json.dumps({
                'success': False,
                'message': 'Invalid token id'
            }).encode('utf-8')

        try:
            token_info = self.manager.tx_storage.tokens_index.get_token_info(
                token_uid)
        except KeyError:
            return json.dumps({
                'success': False,
                'message': 'Unknown token'
            }).encode('utf-8')

        mint = []
        melt = []

        transactions_count = self.manager.tx_storage.tokens_index.get_transactions_count(
            token_uid)

        for tx_hash, index in token_info.mint:
            mint.append({'tx_id': tx_hash.hex(), 'index': index})

        for tx_hash, index in token_info.melt:
            melt.append({'tx_id': tx_hash.hex(), 'index': index})

        data = {
            'name': token_info.name,
            'symbol': token_info.symbol,
            'success': True,
            'mint': mint,
            'melt': melt,
            'total': token_info.total,
            'transactions_count': transactions_count,
        }
        return json.dumps(data).encode('utf-8')
Пример #16
0
    def render_GET(self, request):
        """ Get request to /dashboard-tx/ that return a list of blocks and tx
            We expect two GET parameters: 'block' and 'tx'

            'block': int that indicates de quantity of blocks I should return
            'tx': int that indicates de quantity of tx I should return

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        parsed = parse_get_arguments(request.args, ARGS)
        if not parsed['success']:
            return get_missing_params_msg(parsed['missing'])

        # Get quantity for each
        try:
            block_count = int(parsed['args']['block'])
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid parameter, cannot convert to int: block'
            }).encode('utf-8')

        try:
            tx_count = int(parsed['args']['tx'])
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid parameter, cannot convert to int: tx'
            }).encode('utf-8')

        # Restrict counts
        block_count = min(block_count, settings.MAX_DASHBOARD_COUNT)
        tx_count = min(tx_count, settings.MAX_DASHBOARD_COUNT)

        transactions, _ = self.manager.tx_storage.get_newest_txs(
            count=tx_count)
        serialized_tx = [tx.to_json_extended() for tx in transactions]

        blocks, _ = self.manager.tx_storage.get_newest_blocks(
            count=block_count)
        serialized_blocks = [block.to_json_extended() for block in blocks]

        data = {
            'success': True,
            'transactions': serialized_tx,
            'blocks': serialized_blocks,
        }

        return json.dumps(data, indent=4).encode('utf-8')
Пример #17
0
    def render_GET(self, request):
        """ Get request /wallet/nano-contract/decode/ that returns the tx decoded, if success

        Expects 'hex_tx' as GET parameter

        :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if b'hex_tx' in request.args:
            requested_decode = request.args[b'hex_tx'][0].decode('utf-8')
        else:
            return get_missing_params_msg('hex_tx')

        pattern = r'[a-fA-F\d]+'
        if re.match(pattern,
                    requested_decode) and len(requested_decode) % 2 == 0:
            tx_bytes = bytes.fromhex(requested_decode)

            try:
                tx = Transaction.create_from_struct(tx_bytes)
            except struct.error:
                data = {'success': False, 'message': 'Invalid transaction'}
                return json.dumps(data).encode('utf-8')

            outputs = []
            nano_contract = None
            for _output in tx.outputs:
                _nano_contract = NanoContractMatchValues.parse_script(
                    _output.script)
                if _nano_contract:
                    nano_contract = _nano_contract.to_human_readable()
                    nano_contract['value'] = _output.value
                    continue
                else:
                    outputs.append(_output.to_human_readable())

            my_inputs, other_inputs = self.manager.wallet.separate_inputs(
                tx.inputs, self.manager.tx_storage)

            my_inputs = [_in.to_human_readable() for _in in my_inputs]
            other_inputs = [_in.to_human_readable() for _in in other_inputs]

            data = {
                'success': True,
                'nano_contract': nano_contract,
                'outputs': outputs,
                'my_inputs': my_inputs,
                'other_inputs': other_inputs
            }
        else:
            data = {'success': False, 'message': 'Invalid transaction'}
        return json.dumps(data).encode('utf-8')
Пример #18
0
    def render_POST(self, request: Request) -> Any:
        """ POST request for /thin_wallet/send_tokens/
            We expect 'tx_hex' as request args
            'tx_hex': serialized tx in hexadecimal
            We return success (bool)

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        # Validating if we still have unused threads to solve the pow
        if len(self.manager.pow_thread_pool.working
               ) == settings.MAX_POW_THREADS:
            return self.return_POST(
                False,
                'The server is currently fully loaded to send tokens. Wait a moment and try again, please.'
            )

        post_data = json.loads(request.content.read().decode('utf-8'))
        tx_hex = post_data['tx_hex']

        try:
            tx = tx_or_block_from_bytes(bytes.fromhex(tx_hex))
        except struct.error:
            return self.return_POST(
                False, 'Error parsing hexdump to create the transaction')

        assert isinstance(tx, Transaction)
        # Set tx storage
        tx.storage = self.manager.tx_storage

        # If this tx is a double spending, don't even try to propagate in the network
        is_double_spending = tx.is_double_spending()
        if is_double_spending:
            data = {
                'success':
                False,
                'message':
                'Invalid transaction. At least one of your inputs has already been spent.'
            }
            return json.dumps(data, indent=4).encode('utf-8')

        request.should_stop_mining_thread = False

        if settings.SEND_TOKENS_STRATUM and self.manager.stratum_factory:
            self._render_POST_stratum(tx, request)
        else:
            self._render_POST(tx, request)

        request.notifyFinish().addErrback(self._responseFailed, request)

        from twisted.web.server import NOT_DONE_YET
        return NOT_DONE_YET
Пример #19
0
    def render_GET(self, request):
        """ GET request for /wallet/balance/
            Returns the int balance of the wallet

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        data = {'balance': self.manager.wallet.balance[settings.HATHOR_TOKEN_UID]._asdict()}
        return json.dumps(data, indent=4).encode('utf-8')
Пример #20
0
    def render_POST(self, request):
        """ Creates a nano contract tx and returns it in hexadecimal format.

        Post data should be a json with the following items:
        values: List[{'address', 'value'}], with bet address and value
        fallback_address: if none of the addresses above is the winner, this address
                          can execute the contract
        oracle_pubkey_hash: oracle's public key hashed
        oracle_data_id: oracle's id about this nano contract
        total_value: nano contract total value
        input_value: amount this wallet should stake in the nano contract

        :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        try:
            data = json.loads(request.content.read().decode('utf-8'))
        except json.JSONDecodeError:
            return json.dumps({'success': False, 'message': 'Invalid format for post data'}).encode('utf-8')

        for param in PARAMS_POST:
            if param not in data:
                return get_missing_params_msg(param)

        try:
            decoded_params = self.decode_post_params(data)
        except ValueError as e:
            return json.dumps({'success': False, 'message': e.message}).encode('utf-8')

        nano_contract = NanoContractMatchValues(
            decoded_params.oracle_pubkey_hash, decoded_params.min_timestamp, decoded_params.oracle_data_id,
            decoded_params.value_dict, decoded_params.fallback_address
        )

        tx_outputs = []
        tx_outputs.append(TxOutput(decoded_params.total_value, nano_contract.create_output_script()))

        inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount(
            decoded_params.input_value,
            self.manager.tx_storage
        )
        change_tx = self.manager.wallet.handle_change_tx(total_inputs_amount, decoded_params.input_value)
        if change_tx:
            tx_outputs.append(TxOutput(change_tx.value, P2PKH.create_output_script(change_tx.address)))
        tx_inputs = [TxInput(txin.tx_id, txin.index, b'') for txin in inputs]

        tx = Transaction(inputs=tx_inputs, outputs=tx_outputs)

        ret = {'success': True, 'hex_tx': tx.get_struct().hex()}
        return json.dumps(ret).encode('utf-8')
Пример #21
0
    def render_GET(self, request):
        """ GET request for /wallet/state/
            Returns a boolean saying if the wallet is locked
            'is_locked': True|False

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        data = {'is_locked': self.manager.wallet.is_locked(), 'type': self.manager.wallet.type.value}

        return json.dumps(data, indent=4).encode('utf-8')
Пример #22
0
    def render_POST(self, request):
        """ Lock the wallet

            :return: Boolean if the user locked the wallet with success
            :rtype: string (json) Dict['success', bool]
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        self.manager.wallet.lock()

        ret = {'success': True}
        return json.dumps(ret, indent=4).encode('utf-8')
Пример #23
0
    def render_POST(self, request):
        """ Add p2p peers
            It expects a list of peers, in the format protocol://host:port (tcp://172.121.212.12:40403)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        try:
            peers = json.loads(request.content.read().decode('utf-8'))
        except (json.JSONDecodeError, AttributeError):
            return json.dumps({
                'success': False,
                'message': 'Invalid format for post data'
            }).encode('utf-8')

        if not isinstance(peers, list):
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid format for post data. It was expected a list of strings.'
            }).encode('utf-8')

        known_peers = self.manager.connections.peer_storage.values()

        def already_connected(connection_string: str) -> bool:
            # determines if given connection string is already among connected or connecting peers
            endpoint_url = connection_string.replace('//', '')

            # ignore peers that we're already trying to connect
            if endpoint_url in self.manager.connections.iter_not_ready_endpoints(
            ):
                return True

            # remove peers we already know about
            for peer in known_peers:
                if connection_string in peer.entrypoints:
                    return True

            return False

        filtered_peers = [
            connection_string for connection_string in peers
            if not already_connected(connection_string)
        ]

        pd = BootstrapPeerDiscovery(filtered_peers)
        pd.discover_and_connect(self.manager.connections.connect_to)

        ret = {'success': True, 'peers': filtered_peers}
        return json.dumps(ret, indent=4).encode('utf-8')
Пример #24
0
    def render_GET(self, request):
        """ Get request to /tips-histogram/ that return the number of tips between two timestamp
            We expect two GET parameters: 'begin' and 'end'

            'begin': int that indicates the beginning of the interval
            'end': int that indicates the end of the interval

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        parsed = parse_get_arguments(request.args, ARGS)
        if not parsed['success']:
            return json.dumps({
                'success':
                False,
                'message':
                'Missing parameter: {}'.format(parsed['missing'])
            }).encode('utf-8')

        args = parsed['args']

        # Get quantity for each
        try:
            begin = int(args['begin'])
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid parameter, cannot convert to int: begin'
            }).encode('utf-8')

        try:
            end = int(args['end'])
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid parameter, cannot convert to int: end'
            }).encode('utf-8')

        v = []
        for timestamp in range(begin, end + 1):
            tx_tips = self.manager.tx_storage.get_tx_tips(timestamp)
            v.append((timestamp, len(tx_tips)))

        return json.dumps({'success': True, 'tips': v}).encode('utf-8')
Пример #25
0
    def render_GET(self, request):
        """ GET request for /miners/
            Returns statistics about connected miners

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if not self.manager.stratum_factory:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

        return json.dumps(self.manager.stratum_factory.get_stats_resource()).encode('utf-8')
Пример #26
0
    def render_POST(self, request):
        """ Post request /create_tx/ that returns an encoded tx, if valid

            Expects {"inputs":[{"tx_id": <hex encoded>, "index": <int>, "data": <optional base64 encoded>}],
                     "outputs":[{"value": <int, 1.00 HTR = 100>, "token_uid": <optional omit for HTR, hex encoded>,
                     "address" or "script"}]} as POST data
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        body_content = json_loadb(request.content.read())

        raw_inputs = body_content.get('inputs', [])
        raw_outputs = body_content.get('outputs', [])

        inputs = [TxInput.create_from_dict(i) for i in raw_inputs]
        tokens = []
        outputs = [from_raw_output(i, tokens) for i in raw_outputs]

        timestamp = int(max(self.manager.tx_storage.latest_timestamp, self.manager.reactor.seconds()))
        parents = self.manager.get_new_tx_parents(timestamp)
        # this tx will have to be mined by tx-mining-server or equivalent
        tx = Transaction(
            timestamp=timestamp,
            inputs=inputs,
            outputs=outputs,
            parents=parents,
            storage=self.manager.tx_storage,
        )
        fake_signed_tx = tx.clone()
        for tx_input in fake_signed_tx.inputs:
            # conservative estimate of the input data size to estimate a valid weight
            tx_input.data = b'\0' * 107
        tx.weight = minimum_tx_weight(fake_signed_tx)
        tx.verify_unsigned_skip_pow()

        if tx.is_double_spending():
            raise InvalidNewTransaction('At least one of your inputs has already been spent.')

        hex_data = bytes(tx).hex()
        data = tx.to_json()
        data.pop('hash', None)
        data.pop('nonce', None)

        return json_dumpb({
            'success': True,
            'hex_data': hex_data,
            'data': data,
        })
Пример #27
0
    def render_GET(self, request):
        """ GET request for /websocket_stats/ that returns the stats from Websocket

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        data = {
            'connections':
            len(self.websocket_factory.connections),
            'subscribed_addresses':
            len(self.websocket_factory.address_connections),
        }
        return json.dumps(data, indent=4).encode('utf-8')
Пример #28
0
    def render_GET(self, request):
        """ Get request to /tips/ that return a list of tips hashes

            'timestamp' is an optional expected parameter to be used in the get_tx_tips method

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        timestamp = None
        if b'timestamp' in request.args:
            timestamp = int(request.args[b'timestamp'][0])

        tx_tips = self.manager.tx_storage.get_tx_tips(timestamp)
        ret = [tip.data.hex() for tip in tx_tips]
        return json.dumps(ret).encode('utf-8')
    def render_GET(self, request):
        """ Get request /transaction_acc_weight/ that returns the acc_weight data of a tx

            Expects 'id' (hash) as GET parameter of the tx we will return the data

            :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        if b'id' not in request.args:
            return get_missing_params_msg('id')

        requested_hash = request.args[b'id'][0].decode('utf-8')
        data = self._render_GET_data(requested_hash)

        return json.dumps(data, indent=4).encode('utf-8')
Пример #30
0
    def render_GET(self, request: 'Request') -> bytes:
        """ Get request /block_at_height/ that returns a block at height in parameter

            'height': int, the height of block to get

            :rtype: string (json)
        """
        assert self.manager.tx_storage.indexes is not None

        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'GET')

        # Height parameter is required
        parsed = parse_get_arguments(request.args, ['height'])
        if not parsed['success']:
            return get_missing_params_msg(parsed['missing'])

        args = parsed['args']

        # Height parameter must be an integer
        try:
            height = int(args['height'])
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid \'height\' parameter, expected an integer'
            }).encode('utf-8')

        # Get hash of the block with the height
        block_hash = self.manager.tx_storage.indexes.height.get(height)

        # If there is no block in the index with this height, block_hash will be None
        if block_hash is None:
            return json.dumps({
                'success':
                False,
                'message':
                'No block with height {}.'.format(height)
            }).encode('utf-8')

        block = self.manager.tx_storage.get_transaction(block_hash)

        data = {'success': True, 'block': block.to_json_extended()}
        return json.dumps(data, indent=4).encode('utf-8')