diff --git a/src/cutecoin/core/net/api/bma/__init__.py b/src/cutecoin/core/net/api/bma/__init__.py index 0fe95df4ee8ba9787c89867896479217effae55a..4d4054796a33b6ec2beefcf48eb8d8c9ae758421 100644 --- a/src/cutecoin/core/net/api/bma/__init__.py +++ b/src/cutecoin/core/net/api/bma/__init__.py @@ -122,8 +122,8 @@ class API(object): request.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded") reply = self.conn_handler.network_manager.post(request, - post_data.toString(QUrl.FullyEncoded)) - + post_data.toString(QUrl.FullyEncoded).encode('utf-8')) + logging.debug(url.toString(QUrl.FullyEncoded)) return reply from . import network, blockchain, tx, wot diff --git a/src/cutecoin/core/net/api/bma/tx/__init__.py b/src/cutecoin/core/net/api/bma/tx/__init__.py index 4bfc3cf4755224f5e8cd3af43374f60a27253f54..6acdbcacd43ab92ee73795442f77958917ded5fc 100644 --- a/src/cutecoin/core/net/api/bma/tx/__init__.py +++ b/src/cutecoin/core/net/api/bma/tx/__init__.py @@ -35,6 +35,27 @@ class Process(Tx): return self.requests_post('/process', **kwargs) +class History(Tx): + """Get transaction sources.""" + + null_value = { + "currency": "", + "pubkey": "", + "history": { + "sent": [], + "received": [] + } + } + + def __init__(self, conn_handler, pubkey, module='tx'): + super(Tx, self).__init__(conn_handler, module) + self.pubkey = pubkey + + def __get__(self, **kwargs): + assert self.pubkey is not None + return self.requests_get('/history/%s' % self.pubkey, **kwargs) + + class Sources(Tx): """Get transaction sources.""" diff --git a/src/cutecoin/core/net/api/bma/tx/history/__init__.py b/src/cutecoin/core/net/api/bma/tx/history/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c15c847753901d614d817ee8bc3218b0ff8cbd91 --- /dev/null +++ b/src/cutecoin/core/net/api/bma/tx/history/__init__.py @@ -0,0 +1,40 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import History, logging + +logger = logging.getLogger("ucoin/tx") + + +class Blocks(History): + def __init__(self, conn_handler, pubkey, from_, to_, module='blocks'): + super(History, self).__init__(conn_handler, pubkey, module) + self.from_ = from_ + self.to_ = to_ + + null_value = { + "currency": "", + "pubkey": "", + "history": { + "sent": [], + "received": [] + } + } + + def __get__(self, **kwargs): + return self.requests_get('/blocks/{0}/{1}'.format(self.from_, self.to_) **kwargs) diff --git a/src/cutecoin/core/net/api/bma/ud/__init__.py b/src/cutecoin/core/net/api/bma/ud/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..39ef282632a6288d0b103b702e2ef62dfe712235 --- /dev/null +++ b/src/cutecoin/core/net/api/bma/ud/__init__.py @@ -0,0 +1,47 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import API, logging + +logger = logging.getLogger("ucoin/ud") + + +class Ud(API): + def __init__(self, conn_handler, module='ud'): + super(Ud, self).__init__(conn_handler, module) + + + +class History(Ud): + """Get UD history.""" + + null_value = { + "currency": "", + "pubkey": "", + "history": { + "history": [] + } + } + + def __init__(self, conn_handler, pubkey, module='ud'): + super(Ud, self).__init__(conn_handler, module) + self.pubkey = pubkey + + def __get__(self, **kwargs): + assert self.pubkey is not None + return self.requests_get('/history/%s' % self.pubkey, **kwargs) diff --git a/src/cutecoin/core/transfer.py b/src/cutecoin/core/transfer.py index dcfeb9cfbbcad3db288918c9997acfbfafade8ea..4aae112187ed7b2bb3f9951fc0f0cd8de4af061f 100644 --- a/src/cutecoin/core/transfer.py +++ b/src/cutecoin/core/transfer.py @@ -4,11 +4,13 @@ Created on 31 janv. 2015 @author: inso ''' import logging -from ucoinpy.api import bma from ucoinpy.documents.transaction import Transaction +from .net.api import bma as qtbma +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject +from PyQt5.QtNetwork import QNetworkReply +import hashlib - -class Transfer(object): +class Transfer(QObject): ''' A transfer is the lifecycle of a transaction. TO_SEND means the transaction wasn't sent yet @@ -25,7 +27,10 @@ class Transfer(object): REFUSED = 3 DROPPED = 5 - def __init__(self, txdoc, state, metadata): + transfer_broadcasted = pyqtSignal() + broadcast_error = pyqtSignal(str, str) + + def __init__(self, hash, state, metadata): ''' The constructor of a transfer. Check for metadata keys which must be present : @@ -50,7 +55,7 @@ class Transfer(object): assert('receiver_uid' in metadata) assert('txid' in metadata) - self.txdoc = txdoc + self.hash = hash self.state = state self._metadata = metadata @@ -62,22 +67,18 @@ class Transfer(object): return cls(None, Transfer.TO_SEND, metadata) @classmethod - def create_validated(cls, txdoc, metadata): + def create_validated(cls, hash, metadata): ''' Create a new transfer in a "VALIDATED" state. ''' - return cls(txdoc, Transfer.VALIDATED, metadata) + return cls(hash, Transfer.VALIDATED, metadata) @classmethod def load(cls, data): ''' Create a new transfer from a dict in json format. ''' - if data['state'] is Transfer.TO_SEND: - txdoc = None - else: - txdoc = Transaction.from_signed_raw(data['txdoc']) - return cls(txdoc, data['state'], data['metadata']) + return cls(data['hash'], data['state'], data['metadata']) @property def metadata(self): @@ -90,11 +91,7 @@ class Transfer(object): ''' :return: The transfer as a dict in json format ''' - if self.txdoc: - txraw = self.txdoc.signed_raw() - else: - txraw = None - return {'txdoc': txraw, + return {'hash': self.hash, 'state': self.state, 'metadata': self._metadata} @@ -107,18 +104,32 @@ class Transfer(object): :param txdoc: A transaction ucoinpy object :param community: The community target of the transaction ''' - try: - self.txdoc = txdoc - community.broadcast(bma.tx.Process, - post_args={'transaction': self.txdoc.signed_raw()}) - self.state = Transfer.AWAITING - except ValueError as e: - if '400' in str(e): - self.state = Transfer.REFUSED - raise - finally: - self._metadata['block'] = community.current_blockid()['number'] - self._metadata['time'] = community.get_block().mediantime + replies = community.bma_access.broadcast(qtbma.tx.Process, + post_args={'transaction': self.txdoc.signed_raw()}) + for r in replies: + r.finished.connect(lambda reply=r: self.__handle_transfers_reply(replies, reply)) + + self.state = Transfer.AWAITING + self.hash = hashlib.sha1(txdoc.signed_raw().encode("ascii")).hexdigest().upper() + self._metadata['block'] = community.current_blockid()['number'] + self._metadata['time'] = community.get_block().mediantime + + def __handle_transfers_reply(self, replies, reply): + strdata = bytes(reply.readAll()).decode('utf-8') + logging.debug("Received reply : {0} : {1}".format(reply.error(), strdata)) + if reply.error() == QNetworkReply.NoError: + self.transfer_broadcasted.emit() + for r in replies: + try: + r.disconnect() + except TypeError as e: + if "disconnect()" in str(e): + logging.debug("Could not disconnect a reply") + else: + for r in replies: + if not r.isFinished() or r.error() == QNetworkReply.NoError: + return + self.broadcast_error.emit(r.error(), strdata) def check_registered(self, tx, block, time): ''' @@ -154,14 +165,14 @@ class Transfer(object): class Received(Transfer): - def __init__(self, txdoc, metadata): + def __init__(self, hash, metadata): ''' A transfer were the receiver is the local user. :param txdoc: The transaction document of the received transfer :param metadata: The metadata of the transfer ''' - super().__init__(txdoc, Transfer.VALIDATED, metadata) + super().__init__(hash, Transfer.VALIDATED, metadata) @classmethod def load(cls, data): @@ -170,5 +181,4 @@ class Received(Transfer): :param data: The transfer as a dict in json format ''' - txdoc = Transaction.from_signed_raw(data['txdoc']) - return cls(txdoc, data['metadata']) + return cls(data['hash'], data['metadata']) diff --git a/src/cutecoin/core/wallet.py b/src/cutecoin/core/wallet.py index 4be6cdbf9f5923000c5ccab57a12ee973934510f..8053cf60c2ed50d9de91447ca3a4341e85d3b7c7 100644 --- a/src/cutecoin/core/wallet.py +++ b/src/cutecoin/core/wallet.py @@ -66,42 +66,43 @@ class Cache(): def transfers(self): return [t for t in self._transfers if t.state != Transfer.DROPPED] - def _parse_transaction(self, community, tx, block_number, - mediantime, received_list, txid): - #logging.debug(tx.signed_raw()) - receivers = [o.pubkey for o in tx.outputs - if o.pubkey != tx.issuers[0]] + def _parse_transaction(self, community, txdata, received_list, txid): + receivers = [o.pubkey for o in txdata['outputs'] + if o.pubkey != txdata['issuers'][0]] - if len(receivers) == 0: - receivers = [tx.issuers[0]] - - try: - issuer_uid = Person.lookup(tx.issuers[0], community).uid - except LookupFailureError: - issuer_uid = "" + block_number = txdata['block'] + mediantime = txdata['time'] - try: - receiver_uid = Person.lookup(receivers[0], community).uid - except LookupFailureError: - receiver_uid = "" + if len(receivers) == 0: + receivers = [txdata['issuers'][0]] + # + # try: + # issuer_uid = IdentitiesRegistry.lookup(txdata['issuers'][0], community).uid + # except LookupFailureError: + # issuer_uid = "" + # + # try: + # receiver_uid = IdentitiesRegistry.lookup(receivers[0], community).uid + # except LookupFailureError: + # receiver_uid = "" metadata = {'block': block_number, 'time': mediantime, - 'comment': tx.comment, - 'issuer': tx.issuers[0], - 'issuer_uid': issuer_uid, + 'comment': txdata['comment'], + 'issuer': txdata['issuers'][0], + 'issuer_uid': "",#issuer_uid, 'receiver': receivers[0], - 'receiver_uid': receiver_uid, + 'receiver_uid': "",#receiver_uid, 'txid': txid} - in_issuers = len([i for i in tx.issuers + in_issuers = len([i for i in txdata['issuers'] if i == self.wallet.pubkey]) > 0 - in_outputs = len([o for o in tx.outputs + in_outputs = len([o for o in txdata['outputs'] if o.pubkey == self.wallet.pubkey]) > 0 # If the wallet pubkey is in the issuers we sent this transaction if in_issuers: - outputs = [o for o in tx.outputs + outputs = [o for o in txdata['outputs'] if o.pubkey != self.wallet.pubkey] amount = 0 for o in outputs: @@ -111,49 +112,23 @@ class Cache(): awaiting = [t for t in self._transfers if t.state == Transfer.AWAITING] # We check if the transaction correspond to one we sent - if tx.signed_raw() not in [t.txdoc.signed_raw() for t in awaiting]: - transfer = Transfer.create_validated(tx, + if txdata['hash'] not in [t['hash'] for t in awaiting]: + transfer = Transfer.create_validated(txdata, metadata.copy()) self._transfers.append(transfer) # If we are not in the issuers, # maybe it we are in the recipients of this transaction elif in_outputs: - outputs = [o for o in tx.outputs + outputs = [o for o in txdata.outputs if o.pubkey == self.wallet.pubkey] amount = 0 for o in outputs: amount += o.amount metadata['amount'] = amount - received = Received(tx, metadata.copy()) + received = Received(txdata, metadata.copy()) received_list.append(received) self._transfers.append(received) - - def _parse_block(self, community, block_number, received_list): - block = community.request(bma.blockchain.Block, - req_args={'number': block_number}) - signed_raw = "{0}{1}\n".format(block['raw'], - block['signature']) - try: - block_doc = Block.from_signed_raw(signed_raw) - except: - logging.debug("Error in {0}".format(block_number)) - raise - for (txid, tx) in enumerate(block_doc.transactions): - self._parse_transaction(community, tx, block_number, - block_doc.mediantime, received_list, - txid) - - logging.debug("Received {0} transactions".format(len(received_list))) - awaiting = [t for t in self._transfers - if t.state == Transfer.AWAITING] - # After we checked all transactions, we check if - # sent transactions still waiting for validation - # have to be considered refused - for transfer in awaiting: - transfer.check_registered(tx, block_number, - block_doc.mediantime) - def refresh(self, community, received_list): current_block = 0 try: @@ -163,22 +138,14 @@ class Cache(): # Lets look if transactions took too long to be validated awaiting = [t for t in self._transfers if t.state == Transfer.AWAITING] - with_tx = community.request(bma.blockchain.TX) + tx_history = community.bma_access.request(qtbma.tx.history.Blocks, + req_args={'pubkey': self.wallet.pubkey, + 'from_':self.latest_block, + 'to_': self.current_block}) # We parse only blocks with transactions - parsed_blocks = reversed(range(self.latest_block + 1, - current_block + 1)) - logging.debug("Refresh from {0} to {1}".format(self.latest_block + 1, - current_block + 1)) - parsed_blocks = [n for n in parsed_blocks - if n in with_tx['result']['blocks']] - logging.debug(parsed_blocks) - self.wallet.refresh_progressed.emit(self.latest_block, current_block) - - for block_number in parsed_blocks: - self._parse_block(community, block_number, received_list) - self.wallet.refresh_progressed.emit(current_block - block_number, - current_block - self.latest_block) + for (txid, txdata) in enumerate(tx_history['history']['received'] + tx_history['history']['sent']): + self._parse_transaction(community, txdata, received_list, txid) if current_block > self.latest_block: self.available_sources = self.wallet.sources(community) @@ -397,12 +364,12 @@ class Wallet(QObject): logging.debug("Sender pubkey:{0}".format(key.pubkey)) try: - issuer_uid = Person.lookup(key.pubkey, community).uid + issuer_uid = self.identities_registry.lookup(key.pubkey, community).uid except LookupFailureError: issuer_uid = "" try: - receiver_uid = Person.lookup(recipient, community).uid + receiver_uid = self.identities_registry.lookup(recipient, community).uid except LookupFailureError: receiver_uid = ""