Esempio n. 1
0
    def deprecated_resource(self, request: Request) -> bytes:
        """ This resource is deprecated. It's here only to keep
            compatibility with old wallet versions
        """
        if b'addresses[]' not in request.args:
            return get_missing_params_msg('addresses[]')

        addresses = request.args[b'addresses[]']
        history = []
        seen: Set[bytes] = set()
        for address_to_decode in addresses:
            address = address_to_decode.decode('utf-8')
            try:
                decode_address(address)
            except InvalidAddress:
                return json.dumps({
                    'success':
                    False,
                    'message':
                    'The address {} is invalid'.format(address)
                }).encode('utf-8')

            for tx_hash in self.manager.tx_storage.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')
Esempio n. 2
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')
Esempio n. 3
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')
Esempio n. 4
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')
Esempio n. 5
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')
Esempio n. 6
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')
    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')
    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')
Esempio n. 9
0
    def render_GET(self, request):
        """ Get request /decode_tx/ that returns the signed tx, 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)

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

            try:
                tx = Transaction.create_from_struct(tx_bytes)
                tx.storage = self.manager.tx_storage
                self.manager.wallet.sign_transaction(tx,
                                                     self.manager.tx_storage)

                if prepare_to_send:
                    tx.parents = self.manager.get_new_tx_parents()
                    tx.update_timestamp(int(self.manager.reactor.seconds()))
                    tx.weight = self.manager.minimum_tx_weight(tx)
                    tx.resolve()

                data = {'hex_tx': tx.get_struct().hex(), 'success': True}
            except struct.error:
                data = {'success': False, 'message': 'Transaction invalid'}

        else:
            data = {'success': False, 'message': 'Transaction invalid'}
        return json.dumps(data).encode('utf-8')
Esempio n. 10
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.wallet_index:
            request.setResponseCode(503)
            return json.dumps({'success': False}, indent=4).encode('utf-8')

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

        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'))
Esempio n. 11
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token_history/

            Expects as GET parameter of the queried token:
                - 'id': uid of token whose history is being requested
                - 'count': int, to indicate the quantity of elements we should return
                - 'hash': string, the hash reference we are in the pagination
                - 'timestamp': int, the timestamp reference we are in the pagination
                - 'page': 'previous' or 'next', to indicate if the user wants after or before the hash reference

            :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')

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

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

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

        try:
            count = min(int(parsed['args']['count']), settings.MAX_TX_COUNT)
        except ValueError:
            return json.dumps({
                'success': False,
                'message': 'Invalid \'count\' parameter, expected an int'
            }).encode('utf-8')

        if b'hash' in request.args:
            parsed = parse_get_arguments(request.args, ['timestamp', 'page', 'hash'])
            if not parsed['success']:
                return get_missing_params_msg(parsed['missing'])

            try:
                hash_bytes = bytes.fromhex(parsed['args']['hash'])
            except ValueError:
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'hash\' parameter, could not decode hexadecimal'
                }).encode('utf-8')

            page = parsed['args']['page']
            if page != 'previous' and page != 'next':
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'page\' parameter, expected \'previous\' or \'next\''
                }).encode('utf-8')

            try:
                ref_timestamp = int(parsed['args']['timestamp'])
            except ValueError:
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'timestamp\' parameter, expected an int'
                }).encode('utf-8')

            if page == 'previous':
                elements, has_more = self.manager.tx_storage.tokens_index.get_newer_transactions(
                        token_uid, ref_timestamp, hash_bytes, count)
            else:
                elements, has_more = self.manager.tx_storage.tokens_index.get_older_transactions(
                        token_uid, ref_timestamp, hash_bytes, count)
        else:
            elements, has_more = self.manager.tx_storage.tokens_index.get_newest_transactions(token_uid, count)

        transactions = [self.manager.tx_storage.get_transaction(element) for element in elements]
        serialized = [tx.to_json_extended() for tx in transactions]

        data = {
            'success': True,
            'transactions': serialized,
            'has_more': has_more,
        }
        return json.dumps(data).encode('utf-8')
Esempio n. 12
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_search/
            Expects 'address' and 'count' as required request args
            'token' is an optional parameter to define if the search wants an specific token only
            'hash' and 'page' are optional args to be used in pagination

            'address' is a base58 address string
            'count' is an integer indicating the quantity of elements to be returned
            'token' is the token uid in hex to be searched
            'hash' is the first address of the pagination to start the history
            'page' is either 'previous' or 'next' to indicate the page clicked

            Returns an array of WalletIndex until the count limit and the hash
            parameter for the next request, if has more
        """
        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')

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

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

        try:
            address = request.args[b'address'][0].decode('utf-8')
            # Check if address is valid
            decode_address(address)
        except InvalidAddress:
            return json.dumps({
                'success': False,
                'message': 'Invalid \'address\' parameter'
            }).encode('utf-8')

        try:
            count = min(int(request.args[b'count'][0]), settings.MAX_TX_COUNT)
        except ValueError:
            return json.dumps({
                'success':
                False,
                'message':
                'Invalid \'count\' parameter, expected an int'
            }).encode('utf-8')

        token_uid_bytes = None
        if b'token' in request.args:
            # It's an optional parameter, we just check if it's a valid hex
            token_uid = request.args[b'token'][0].decode('utf-8')

            try:
                token_uid_bytes = bytes.fromhex(token_uid)
            except ValueError:
                return json.dumps({
                    'success':
                    False,
                    'message':
                    'Token uid is not a valid hexadecimal value.'
                }).encode('utf-8')

        hashes = wallet_index.get_from_address(address)
        # XXX To do a timestamp sorting, so the pagination works better
        # we must get all transactions and sort them
        # This is not optimal for performance
        transactions = []
        for tx_hash in hashes:
            tx = self.manager.tx_storage.get_transaction(tx_hash)
            if token_uid_bytes and not self.has_token_and_address(
                    tx, address, token_uid_bytes):
                # Request wants to filter by token but tx does not have this token
                # so we don't add it to the transactions array
                continue
            transactions.append(tx.to_json_extended())

        sorted_transactions = sorted(transactions,
                                     key=lambda tx: tx['timestamp'],
                                     reverse=True)
        if b'hash' in request.args:
            # It's a paginated request, so 'page' must also be in request.args
            if b'page' not in request.args:
                return get_missing_params_msg('page')

            page = request.args[b'page'][0].decode('utf-8')
            if page != 'previous' and page != 'next':
                # Invalid value for page parameter
                return json.dumps(
                    {
                        'success': False,
                        'message': 'Invalid value for \'page\' parameter',
                    },
                    indent=4).encode('utf-8')

            ref_hash = request.args[b'hash'][0].decode('utf-8')
            # Index where the reference hash is
            for ref_index, tx in enumerate(sorted_transactions):
                if tx['tx_id'] == ref_hash:
                    break
            else:
                # ref_hash is not in the list
                return json.dumps(
                    {
                        'success': False,
                        'message': 'Invalid hash {}'.format(ref_hash)
                    },
                    indent=4).encode('utf-8')

            if page == 'next':
                # User clicked on 'Next' button, so the ref_hash is the last hash of the list
                # So I need to get the transactions after the ref
                start_index = ref_index + 1
                end_index = start_index + count
                ret_transactions = sorted_transactions[start_index:end_index]
                # If has more transactions after this
                has_more = len(sorted_transactions) > end_index
            else:
                # User clicked on 'Previous' button, so the ref_hash is the first hash of the list
                # So I need to get the transactions before the ref
                end_index = ref_index
                start_index = max(end_index - count, 0)
                ret_transactions = sorted_transactions[start_index:end_index]
                # If has more transactions before this
                has_more = start_index > 0
        else:
            ret_transactions = sorted_transactions[:count]
            has_more = len(sorted_transactions) > count

        data = {
            'success': True,
            'transactions': ret_transactions,
            'has_more': has_more,
            'total': len(sorted_transactions),
        }
        return json.dumps(data, indent=4).encode('utf-8')
Esempio n. 13
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_balance/
            Expects 'address' as request args

            :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
        tokens_index = self.manager.tx_storage.tokens_index

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

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

        try:
            # Check if address is valid
            decode_address(requested_address)
        except InvalidAddress:
            return json.dumps({
                'success': False,
                'message': 'Invalid \'address\' parameter'
            }).encode('utf-8')

        tokens_data: Dict[bytes, TokenData] = defaultdict(TokenData)
        tx_hashes = wallet_index.get_from_address(requested_address)
        for tx_hash in tx_hashes:
            tx = self.manager.tx_storage.get_transaction(tx_hash)
            meta = tx.get_metadata(force_reload=True)
            if not meta.voided_by:
                # We consider the spent/received values only if is not voided by
                for tx_input in tx.inputs:
                    tx2 = self.manager.tx_storage.get_transaction(
                        tx_input.tx_id)
                    tx2_output = tx2.outputs[tx_input.index]
                    if self.has_address(tx2_output, requested_address):
                        # We just consider the address that was requested
                        token_uid = tx2.get_token_uid(
                            tx2_output.get_token_index())
                        tokens_data[token_uid].spent += tx2_output.value

                for tx_output in tx.outputs:
                    if self.has_address(tx_output, requested_address):
                        # We just consider the address that was requested
                        token_uid = tx.get_token_uid(
                            tx_output.get_token_index())
                        tokens_data[token_uid].received += tx_output.value

        return_tokens_data: Dict[str, Dict[str, Any]] = {}
        for token_uid in tokens_data.keys():
            if token_uid == settings.HATHOR_TOKEN_UID:
                tokens_data[token_uid].name = settings.HATHOR_TOKEN_NAME
                tokens_data[token_uid].symbol = settings.HATHOR_TOKEN_SYMBOL
            else:
                try:
                    token_info = tokens_index.get_token_info(token_uid)
                    tokens_data[token_uid].name = token_info.name
                    tokens_data[token_uid].symbol = token_info.symbol
                except KeyError:
                    # Should never get here because this token appears in our wallet index
                    # But better than get a 500 error
                    tokens_data[
                        token_uid].name = '- (unable to fetch token information)'
                    tokens_data[
                        token_uid].symbol = '- (unable to fetch token information)'
            return_tokens_data[
                token_uid.hex()] = tokens_data[token_uid].to_dict()

        data = {
            'success': True,
            'total_transactions': len(tx_hashes),
            'tokens_data': return_tokens_data
        }
        return json.dumps(data, indent=4).encode('utf-8')
Esempio n. 14
0
    def render_PUT(self, request):
        """ Updates a nano contract tx and returns it in hexadecimal format.

        Post data should be a json with the following items:
        hex_tx: tx being updated, in hex value
        new_values: List[{'address', 'value'}], with bet address and 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, 'PUT')

        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_PUT:
            if param not in data:
                return get_missing_params_msg(param)

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

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

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

        if not nano_contract:
            return json.dumps({'success': False, 'message': 'Nano contract not found'}).encode('utf-8')

        for address, value in decoded_params.new_value_dict.items():
            nano_contract.value_dict[address] = value

        tx.outputs = tx_outputs

        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.outputs.insert(0, TxOutput(total_value, nano_contract.create_output_script()))

        for txin in inputs:
            tx.inputs.append(TxInput(txin.tx_id, txin.index, b''))

        ret = {'success': True, 'hex_tx': tx.get_struct().hex()}
        return json.dumps(ret).encode('utf-8')
Esempio n. 15
0
    def get_list_tx(self, request):
        """ Get parameter from request.args and return list of blocks/txs

            'type': 'block' or 'tx', to indicate if we should return a list of blocks or tx
            'count': int, to indicate the quantity of elements we should return
            'hash': string, the hash reference we are in the pagination
            'timestamp': int, the timestamp reference we are in the pagination
            'page': 'previous' or 'next', to indicate if the user wants after or before the hash reference
        """
        parsed = parse_get_arguments(request.args, GET_LIST_ARGS)
        if not parsed['success']:
            return get_missing_params_msg(parsed['missing'])

        args = parsed['args']
        error = None

        try:
            count = min(int(args['count']), settings.MAX_TX_COUNT)
        except ValueError:
            error = {'success': False, 'message': 'Invalid \'count\' parameter, expected an integer'}

        type_tx = args['type']
        if type_tx != 'tx' and type_tx != 'block':
            error = {'success': False, 'message': 'Invalid \'type\' parameter, expected \'block\' or \'tx\''}

        if error:
            return json.dumps(error).encode('utf-8')

        ref_hash = None
        page = ''
        if b'hash' in request.args:
            ref_hash = request.args[b'hash'][0].decode('utf-8')

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

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

            page = parsed['args']['page']
            if page != 'previous' and page != 'next':
                return json.dumps({
                    'success': False,
                    'message': 'Invalid \'page\' parameter, expected \'previous\' or \'next\''
                }).encode('utf-8')

            if type_tx == 'block':
                if page == 'previous':
                    elements, has_more = self.manager.tx_storage.get_newer_blocks_after(
                        ref_timestamp, bytes.fromhex(ref_hash), count)
                else:
                    elements, has_more = self.manager.tx_storage.get_older_blocks_after(
                        ref_timestamp, bytes.fromhex(ref_hash), count)

            else:
                if page == 'previous':
                    elements, has_more = self.manager.tx_storage.get_newer_txs_after(
                        ref_timestamp, bytes.fromhex(ref_hash), count)
                else:
                    elements, has_more = self.manager.tx_storage.get_older_txs_after(
                        ref_timestamp, bytes.fromhex(ref_hash), count)
        else:
            if type_tx == 'block':
                elements, has_more = self.manager.tx_storage.get_newest_blocks(count=count)
            else:
                elements, has_more = self.manager.tx_storage.get_newest_txs(count=count)

        serialized = [element.to_json_extended() for element in elements]

        data = {'transactions': serialized, 'has_more': has_more}
        return json.dumps(data, indent=4).encode('utf-8')
Esempio n. 16
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/address_history/

            If 'paginate' parameter exists, it calls the new resource method
            otherwise, it will call the old and deprecated one because it's
            a request from a wallet still in an older version

            Expects 'addresses[]' as request args, and 'hash'
            as optional args to be used in pagination

            'addresses[]' is an array of address
            'hash' is the hash of the first tx of the pagination to start the history

            Returns an array of WalletIndex for each address until the maximum number

            E.g. request:

            addresses: ['WYxpdgz11cGGPSdmQPcJVwnLsUu7w5hgjw', 'WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ']

            In the case where address 'WYxpdgz11cGGPSdmQPcJVwnLsUu7w5hgjw' has 3 txs [tx_id1, tx_id2, tx_id3] and
            address 'WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ' also has 3 txs [tx_id4, tx_id5, tx_id6].

            Return: {
                'history': [array with 3 txs from first address and 2 txs from second address],
                'has_more': True, indicating that there are more txs for this request
                'first_address': 'WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ', indicating that the next request should
                                                    start with this address as first element of addresses array
                'first_hash': tx_id6, indicating that the next request should start with this transaction
            }

            So we need to execute one more request to finish getting all transactions. Request:

            addresses: ['WSo6BtjdxSSs7FpSzXYgEXwKZ3643K5iSQ']
            hash: tx_id6

            Important note: different requests may return the same transaction for different addresses.
            We just validate if a transactions was already added in the same request, so e.g. the following case:

            1. tx1 has outputs for addr1 and addr2;
            2. Request to get [addr1, addr2];
            3. First response return txs only for addr1 including tx1;
            4. New request to get the remaining txs for addr1 and the txs for addr2 (including tx1)

            In this case we would return tx1 for both requests because we don't have the txs returned previously.
            We could send in all requests the txs already returned but it does not make much difference now.

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

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

        paginate = b'paginate' in request.args and request.args[b'paginate'][
            0].decode('utf-8') == 'true'

        if paginate:
            # New resource
            if b'addresses[]' not in request.args:
                return get_missing_params_msg('addresses[]')

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

            ref_hash = None
            if b'hash' in request.args:
                # If hash parameter is in the request, it must be a valid hex
                ref_hash = request.args[b'hash'][0].decode('utf-8')

            return self.get_address_history(
                [address.decode('utf-8') for address in addresses], ref_hash)
        else:
            # Old and deprecated resource
            return self.deprecated_resource(request)
Esempio n. 17
0
    def render_POST(self, request):
        """ Creates and propagates a tx to spend a nano contract output.

        Post data should be a json with the following items:
        spent_tx_id: tx id being spent
        spent_tx_index: tx index being spent
        oracle_data: the data provided by the oracle
        oracle_signature: signature of the oracle data
        oracle_pubkey: oracle's public key
        address: the winning address
        value: nano contract total value

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

        content = request.content.read()
        if not content:
            return json.dumps({
                'success': False,
                'message': 'No post data received'
            }).encode('utf-8')

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

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

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

        tx_outputs = []
        tx_outputs.append(
            TxOutput(decoded_data.value,
                     P2PKH.create_output_script(decoded_data.address)))

        input_data = NanoContractMatchValues.create_input_data(
            decoded_data.oracle_data, decoded_data.oracle_signature,
            decoded_data.oracle_pubkey)
        tx_input = TxInput(decoded_data.spent_tx_id,
                           decoded_data.spent_tx_index, input_data)
        tx = Transaction(inputs=[tx_input], outputs=tx_outputs)
        tx.storage = self.manager.tx_storage

        tx.parents = self.manager.get_new_tx_parents()
        tx.update_timestamp(int(self.manager.reactor.seconds()))
        tx.weight = self.manager.minimum_tx_weight(tx)
        tx.resolve()
        success = self.manager.propagate_tx(tx)

        ret = {'success': success, 'hex_tx': tx.get_struct().hex()}
        return json.dumps(ret).encode('utf-8')
Esempio n. 18
0
    def render_GET(self, request: Request) -> bytes:
        """ GET request for /thin_wallet/token_history/

            Expects as GET parameter of the queried token:
                - 'id': token uid the history is being requested
                - 'count': int, to indicate the quantity of elements we should return
                - 'hash': string, the hash reference we are in the pagination
                - 'timestamp': int, the timestamp reference we are in the pagination
                - 'page': 'previous' or 'next', to indicate if the user wants after or before the hash reference

            :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')

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

        count = min(int(request.args[b'count'][0]), settings.MAX_TX_COUNT)

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

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

            ref_hash = request.args[b'hash'][0].decode('utf-8')
            ref_timestamp = int(request.args[b'timestamp'][0].decode('utf-8'))
            page = request.args[b'page'][0].decode('utf-8')

            if page == 'previous':
                elements, has_more = self.manager.tx_storage.tokens_index.get_newer_transactions(
                    token_uid, ref_timestamp, bytes.fromhex(ref_hash), count)
            else:
                elements, has_more = self.manager.tx_storage.tokens_index.get_older_transactions(
                    token_uid, ref_timestamp, bytes.fromhex(ref_hash), count)
        else:
            elements, has_more = self.manager.tx_storage.tokens_index.get_newest_transactions(
                token_uid, count)

        transactions = [
            self.manager.tx_storage.get_transaction(element)
            for element in elements
        ]
        serialized = [tx.to_json_extended() for tx in transactions]

        data = {
            'success': True,
            'transactions': serialized,
            'has_more': has_more,
        }
        return json.dumps(data).encode('utf-8')