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