def handle_push_tx(self, params: Dict[str, Any]) -> bytes: try: tx_bytes = bytes.fromhex(params['hex_tx']) tx = tx_or_block_from_bytes(tx_bytes) except ValueError: data = { 'success': False, 'message': 'Invalid hexadecimal data', 'can_force': False } except struct.error: data = { 'success': False, 'message': 'This transaction is invalid. Try to decode it first to validate it.', 'can_force': False } else: if tx.is_block: # It's a block and we can't push blocks data = { 'success': False, 'message': 'This transaction is invalid. A transaction must have at least one input', 'can_force': False } else: tx.storage = self.manager.tx_storage # If this tx is a double spending, don't even try to propagate in the network assert isinstance(tx, Transaction) 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.', 'can_force': False } else: success, message = tx.validate_tx_error() if success or params['force']: message = '' try: success = self.manager.propagate_tx( tx, fails_silently=False) except (InvalidNewTransaction, TxValidationError) as e: success = False message = str(e) data = {'success': success, 'message': message} if success: data['tx'] = tx.to_json() else: data = { 'success': success, 'message': message, 'can_force': True } return json_dumpb(data)
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')
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 _load_transaction_from_filepath(self, filepath): try: with open(filepath, 'rb') as fp: tx_bytes = fp.read() tx = tx_or_block_from_bytes(tx_bytes) tx.storage = self tx.update_hash() return tx except FileNotFoundError: raise TransactionDoesNotExist
def _load_from_bytes(self, tx_data: bytes, meta_data: bytes) -> 'BaseTransaction': from hathor.transaction.base_transaction import tx_or_block_from_bytes from hathor.transaction.transaction_metadata import TransactionMetadata tx = tx_or_block_from_bytes(tx_data) tx._metadata = TransactionMetadata.create_from_json( json_loadb(meta_data)) tx.storage = self return tx
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
def do_mining_submit(self, hexdata: str, optimistic: bool = False) -> Union[bool, Dict]: if not self.factory.manager.can_start_mining(): self.log.warn('node syncing') return False tx = tx_or_block_from_bytes(bytes.fromhex(hexdata), storage=self.factory.manager.tx_storage) if not tx.is_block: self.log.warn('expected Block, received Transaction', data=hexdata) return False res = self.factory.manager.submit_block(tx) if res and optimistic: return self.manager.make_block_template(tx.hash).to_dict() return res
def handle_data(self, payload: str) -> None: """ Handle a received DATA message. """ if not payload: return data = base64.b64decode(payload) try: tx = tx_or_block_from_bytes(data) except struct.error: # Invalid data for tx decode return assert tx is not None assert tx.hash is not None self.log.debug('tx received from peer', tx=tx.hash_hex, peer=self.protocol.get_peer_id()) if self.protocol.node.tx_storage.get_genesis(tx.hash): # We just got the data of a genesis tx/block. What should we do? # Will it reduce peer reputation score? return tx.storage = self.protocol.node.tx_storage assert tx.hash is not None key = self.get_data_key(tx.hash) deferred = self.deferred_by_key.pop(key, None) if deferred: # Adding to the DAG will be done after the downloader validates the correct order assert tx.timestamp is not None self.requested_data_arrived(tx.timestamp) deferred.callback(tx) elif self.manager.tx_storage.transaction_exists(tx.hash): # transaction already added to the storage, ignore it # XXX: maybe we could add a hash blacklist and punish peers propagating known bad txs return else: self.log.info('tx received in real time from peer', tx=tx.hash_hex, peer=self.protocol.get_peer_id()) # If we have not requested the data, it is a new transaction being propagated # in the network, thus, we propagate it as well. result = self.manager.on_new_tx(tx, conn=self.protocol, propagate_to_peers=True) self.update_received_stats(tx, result)
def render_POST(self, request): """ POST request for /submit_block/ """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') data = json_loadb(request.content.read()) tx = tx_or_block_from_bytes(bytes.fromhex(data['hexdata']), storage=self.manager.tx_storage) if not tx.is_block: self.log.debug('expected Block, received Transaction', data=data) raise APIError('Not a block') if not self.manager.can_start_mining(): self.log.debug('cannot propagate Block, node syncing', data=data) raise APIError('Node syncing') res = self.manager.submit_block(tx) return json_dumpb({'result': res})
def render_GET(self, request): """ GET request for /push_tx/ Expects 'hex_tx' as args parameter that is the hex representation of the whole tx :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') requested_decode = request.args[b'hex_tx'][0].decode('utf-8') 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) except struct.error: data = { 'success': False, 'message': 'This transaction is invalid. Try to decode it first to validate it.', 'can_force': False } else: if len(tx.inputs) == 0: # It's a block and we can't push blocks data = { 'success': False, 'message': 'This transaction is invalid. A transaction must have at least one input', 'can_force': False } else: 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.', 'can_force': False } else: success, message = tx.validate_tx_error() force = b'force' in request.args and request.args[ b'force'][0].decode('utf-8') == 'true' if success or force: message = '' try: success = self.manager.propagate_tx( tx, fails_silently=False) except (InvalidNewTransaction, TxValidationError) as e: success = False message = str(e) data = {'success': success, 'message': message} else: data = { 'success': success, 'message': message, 'can_force': True } else: data = { 'success': False, 'message': 'This transaction is invalid. Try to decode it first to validate it.', 'can_force': False } return json.dumps(data, indent=4).encode('utf-8')