diff --git a/src/cutecoin/core/transfer.py b/src/cutecoin/core/transfer.py index 9b2e2da5e2b5acc44855201df8106e6ed94c0622..c7edf796b642f4c3d0141c874de5c94897ac9c43 100644 --- a/src/cutecoin/core/transfer.py +++ b/src/cutecoin/core/transfer.py @@ -4,7 +4,7 @@ Created on 31 janv. 2015 @author: inso ''' import logging -from ucoinpy.documents.transaction import Transaction +import asyncio from .net.api import bma as qtbma from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject from PyQt5.QtNetwork import QNetworkReply @@ -28,7 +28,7 @@ class Transfer(QObject): DROPPED = 5 transfer_broadcasted = pyqtSignal(str) - broadcast_error = pyqtSignal(str, str) + broadcast_error = pyqtSignal(int, str) def __init__(self, hash, state, metadata): ''' @@ -96,6 +96,7 @@ class Transfer(QObject): 'state': self.state, 'metadata': self._metadata} + @asyncio.coroutine def send(self, txdoc, community): ''' Send a transaction and update the transfer state to AWAITING if accepted. @@ -106,14 +107,15 @@ class Transfer(QObject): :param community: The community target of the transaction ''' replies = community.bma_access.broadcast(qtbma.tx.Process, - post_args={'transaction': self.txdoc.signed_raw()}) + post_args={'transaction': 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 + blockid = yield from community.blockid() + self._metadata['block'] = blockid['number'] + self._metadata['time'] = community.get_block()['medianTime'] def __handle_transfers_reply(self, replies, reply): strdata = bytes(reply.readAll()).decode('utf-8') diff --git a/src/cutecoin/core/txhistory.py b/src/cutecoin/core/txhistory.py new file mode 100644 index 0000000000000000000000000000000000000000..67b852b60e1b644b695cadd8f539d63b6d3a0b65 --- /dev/null +++ b/src/cutecoin/core/txhistory.py @@ -0,0 +1,169 @@ +import asyncio +import logging +from .transfer import Transfer, Received +from ucoinpy.documents.transaction import InputSource, OutputSource, Transaction +from ..tools.exceptions import LookupFailureError + +class TxHistory(): + def __init__(self, wallet): + self._latest_block = 0 + self.wallet = wallet + + self._transfers = [] + self.available_sources = [] + + @property + def latest_block(self): + return self._latest_block + + @latest_block.setter + def latest_block(self, value): + self._latest_block = value + + def load_from_json(self, data): + self._transfers = [] + + data_sent = data['transfers'] + for s in data_sent: + if s['metadata']['issuer'] == self.wallet.pubkey: + self._transfers.append(Transfer.load(s)) + else: + self._transfers.append(Received.load(s)) + + for s in data['sources']: + self.available_sources.append(InputSource.from_inline(s['inline'])) + + self.latest_block = data['latest_block'] + + def jsonify(self): + data_transfer = [] + for s in self.transfers: + data_transfer.append(s.jsonify()) + + data_sources = [] + for s in self.available_sources: + s.index = 0 + data_sources.append({'inline': "{0}\n".format(s.inline())}) + + return {'latest_block': self.latest_block, + 'transfers': data_transfer, + 'sources': data_sources} + + @property + def transfers(self): + return [t for t in self._transfers if t.state != Transfer.DROPPED] + + @asyncio.coroutine + def _parse_transaction(self, community, txdata, received_list, txid): + if len(txdata['issuers']) == 0: + True + + tx_outputs = [OutputSource.from_inline(o) for o in txdata['outputs']] + receivers = [o.pubkey for o in tx_outputs + if o.pubkey != txdata['issuers'][0]] + + block_number = txdata['block_number'] + mediantime = txdata['time'] + logging.debug(txdata) + + if len(receivers) == 0: + receivers = [txdata['issuers'][0]] + + try: + issuer = yield from self.wallet._identities_registry.future_lookup(txdata['issuers'][0], community) + issuer_uid = issuer.uid + except LookupFailureError: + issuer_uid = "" + + try: + receiver = yield from self.wallet._identities_registry.future_lookup(receivers[0], community) + receiver_uid = receiver.uid + except LookupFailureError: + receiver_uid = "" + + metadata = {'block': block_number, + 'time': mediantime, + 'comment': txdata['comment'], + 'issuer': txdata['issuers'][0], + 'issuer_uid': issuer_uid, + 'receiver': receivers[0], + 'receiver_uid': receiver_uid, + 'txid': txid} + + in_issuers = len([i for i in txdata['issuers'] + if i == self.wallet.pubkey]) > 0 + in_outputs = len([o for o in tx_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 + if o.pubkey != self.wallet.pubkey] + amount = 0 + for o in outputs: + amount += o.amount + metadata['amount'] = amount + + awaiting = [t for t in self._transfers + if t.state == Transfer.AWAITING] + # We check if the transaction correspond to one we sent + if txdata['hash'] not in [t['hash'] for t in awaiting]: + transfer = Transfer.create_validated(txdata['hash'], + 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 + if o.pubkey == self.wallet.pubkey] + amount = 0 + for o in outputs: + amount += o.amount + metadata['amount'] = amount + received = Received(txdata['hash'], metadata.copy()) + received_list.append(received) + self._transfers.append(received) + return True + + @asyncio.coroutine + def refresh(self, community, received_list): + """ + Refresh last transactions + + :param cutecoin.core.Community community: The community + :param list received_list: List of transactions received + """ + parsed_block = self.latest_block + block_data = yield from community.blockid() + current_block = block_data['number'] + logging.debug("Refresh from : {0} to {1}".format(self.latest_block, current_block)) + + # Lets look if transactions took too long to be validated + awaiting = [t for t in self._transfers + if t.state == Transfer.AWAITING] + while parsed_block < current_block: + tx_history = yield from community.bma_access.future_request(qtbma.tx.history.Blocks, + req_args={'pubkey': self.wallet.pubkey, + 'from_':str(parsed_block), + 'to_': str(parsed_block + 100)}) + + # We parse only blocks with transactions + transactions = tx_history['history']['received'] + tx_history['history']['sent'] + for (txid, txdata) in enumerate(transactions): + if len(txdata['issuers']) == 0: + logging.debug("Error with : {0}, from {1} to {2}".format(self.wallet.pubkey, + parsed_block, + current_block)) + else: + yield from self._parse_transaction(community, txdata, received_list, txid) + self.wallet.refresh_progressed.emit(parsed_block, current_block, self.wallet.pubkey) + parsed_block += 101 + + if current_block > self.latest_block: + self.available_sources = yield from self.wallet.future_sources(community) + self.latest_block = current_block + + for transfer in awaiting: + transfer.check_refused(current_block) + + self.wallet.refresh_finished.emit(received_list) diff --git a/src/cutecoin/core/wallet.py b/src/cutecoin/core/wallet.py index ff722a0d53e5778c899e17d05c5a267ff1efd415..949b5f3c6a5db0c99fa5cfb73700d0e801a09eb9 100644 --- a/src/cutecoin/core/wallet.py +++ b/src/cutecoin/core/wallet.py @@ -11,6 +11,7 @@ from .net.api import bma as qtbma from .net.api.bma import PROTOCOL_VERSION from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable, LookupFailureError from .transfer import Transfer, Received +from .txhistory import TxHistory from .registry import IdentitiesRegistry, Identity from PyQt5.QtCore import QObject, pyqtSignal, QCoreApplication @@ -19,171 +20,6 @@ import logging import asyncio -class Cache(): - def __init__(self, wallet): - self._latest_block = 0 - self.wallet = wallet - - self._transfers = [] - self.available_sources = [] - - @property - def latest_block(self): - return self._latest_block - - @latest_block.setter - def latest_block(self, value): - self._latest_block = value - - def load_from_json(self, data): - self._transfers = [] - - data_sent = data['transfers'] - for s in data_sent: - if s['metadata']['issuer'] == self.wallet.pubkey: - self._transfers.append(Transfer.load(s)) - else: - self._transfers.append(Received.load(s)) - - for s in data['sources']: - self.available_sources.append(InputSource.from_inline(s['inline'])) - - self.latest_block = data['latest_block'] - - def jsonify(self): - data_transfer = [] - for s in self.transfers: - data_transfer.append(s.jsonify()) - - data_sources = [] - for s in self.available_sources: - s.index = 0 - data_sources.append({'inline': "{0}\n".format(s.inline())}) - - return {'latest_block': self.latest_block, - 'transfers': data_transfer, - 'sources': data_sources} - - @property - def transfers(self): - return [t for t in self._transfers if t.state != Transfer.DROPPED] - - @asyncio.coroutine - def _parse_transaction(self, community, txdata, received_list, txid): - if len(txdata['issuers']) == 0: - True - - tx_outputs = [OutputSource.from_inline(o) for o in txdata['outputs']] - receivers = [o.pubkey for o in tx_outputs - if o.pubkey != txdata['issuers'][0]] - - block_number = txdata['block_number'] - mediantime = txdata['time'] - logging.debug(txdata) - - if len(receivers) == 0: - receivers = [txdata['issuers'][0]] - - try: - issuer = yield from self.wallet._identities_registry.future_lookup(txdata['issuers'][0], community) - issuer_uid = issuer.uid - except LookupFailureError: - issuer_uid = "" - - try: - receiver = yield from self.wallet._identities_registry.future_lookup(receivers[0], community) - receiver_uid = receiver.uid - except LookupFailureError: - receiver_uid = "" - - metadata = {'block': block_number, - 'time': mediantime, - 'comment': txdata['comment'], - 'issuer': txdata['issuers'][0], - 'issuer_uid': issuer_uid, - 'receiver': receivers[0], - 'receiver_uid': receiver_uid, - 'txid': txid} - - in_issuers = len([i for i in txdata['issuers'] - if i == self.wallet.pubkey]) > 0 - in_outputs = len([o for o in tx_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 - if o.pubkey != self.wallet.pubkey] - amount = 0 - for o in outputs: - amount += o.amount - metadata['amount'] = amount - - awaiting = [t for t in self._transfers - if t.state == Transfer.AWAITING] - # We check if the transaction correspond to one we sent - if txdata['hash'] not in [t['hash'] for t in awaiting]: - transfer = Transfer.create_validated(txdata['hash'], - 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 - if o.pubkey == self.wallet.pubkey] - amount = 0 - for o in outputs: - amount += o.amount - metadata['amount'] = amount - received = Received(txdata['hash'], metadata.copy()) - received_list.append(received) - self._transfers.append(received) - return True - - @asyncio.coroutine - def refresh(self, community, received_list): - """ - Refresh last transactions - - :param cutecoin.core.Community community: The community - :param list received_list: List of transactions received - """ - parsed_block = 0 - block_data = yield from community.blockid() - current_block = block_data['number'] - logging.debug("Refresh from : {0} to {1}".format(self.latest_block, current_block)) - - # Lets look if transactions took too long to be validated - awaiting = [t for t in self._transfers - if t.state == Transfer.AWAITING] - while parsed_block < current_block: - tx_history = yield from community.bma_access.future_request(qtbma.tx.history.Blocks, - req_args={'pubkey': self.wallet.pubkey, - 'from_':str(parsed_block), - 'to_': str(parsed_block + 100)}) - - # We parse only blocks with transactions - transactions = tx_history['history']['received'] + tx_history['history']['sent'] - for (txid, txdata) in enumerate(transactions): - if len(txdata['issuers']) == 0: - logging.debug("Error with : {0}, from {1} to {2}".format(self.wallet.pubkey, - parsed_block, - current_block)) - else: - yield from self._parse_transaction(community, txdata, received_list, txid) - self.wallet.refresh_progressed.emit(parsed_block, current_block, self.wallet.pubkey) - parsed_block += 101 - - if current_block > self.latest_block: - self.available_sources = yield from self.wallet.future_sources(community) - self.latest_block = current_block - - for transfer in awaiting: - transfer.check_refused(current_block) - - self.wallet.refresh_finished.emit(received_list) - - class Wallet(QObject): ''' A wallet is used to manage money with a unique key. @@ -248,7 +84,7 @@ class Wallet(QObject): ''' for currency in json_data: if currency != 'version': - self.caches[currency] = Cache(self) + self.caches[currency] = TxHistory(self) self.caches[currency].load_from_json(json_data[currency]) def jsonify_caches(self): @@ -269,7 +105,7 @@ class Wallet(QObject): :param community: The community to refresh its cache ''' if community.currency not in self.caches: - self.caches[community.currency] = Cache(self) + self.caches[community.currency] = TxHistory(self) def refresh_transactions(self, community, received_list): ''' @@ -438,7 +274,7 @@ class Wallet(QObject): result = self.tx_inputs(int(amount), community) inputs = result[0] - self.caches[community.currency].available_sources = result[1] + self.caches[community.currency].available_sources = result[1:] logging.debug("Inputs : {0}".format(inputs)) outputs = self.tx_outputs(recipient, amount, inputs) @@ -450,9 +286,9 @@ class Wallet(QObject): tx.sign([key]) logging.debug("Transaction : {0}".format(tx.signed_raw())) - #transfer.transfer_broadcasted.connect(self.transfer_broadcasted) - #transfer.broadcast_error.connect(self.broadcast_error) - transfer.send(tx, community) + transfer.transfer_broadcasted.connect(self.transfer_broadcasted) + transfer.broadcast_error.connect(self.broadcast_error) + yield from transfer.send(tx, community) @asyncio.coroutine def future_sources(self, community): diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py index fc30c727e91ecbe38e655b8675ac774b9d73e7c5..ccb03d05346a8565ec7aa5018fac729c73e7c46f 100644 --- a/src/cutecoin/gui/mainwindow.py +++ b/src/cutecoin/gui/mainwindow.py @@ -46,9 +46,23 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.app = app self.app.load() + self.initialized = False if self.app.preferences["account"] != "": account = self.app.get_account(self.app.preferences["account"]) self.app.change_current_account(account) + # no default account... + else: + # if at least one account exists, set it as default... + if len(self.app.accounts) > 0: + # capture names sorted alphabetically + names = list(self.app.accounts.keys()) + names.sort() + # set first name in list as default in preferences + self.app.preferences['account'] = names[0] + self.app.save_preferences(self.app.preferences) + # open it + logging.debug("No default account in preferences. Set %s as default account." % names[0]) + self.action_change_account(self.app.get_account(self.app.preferences['account'])) self.password_asker = None @@ -132,24 +146,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): if result == QDialog.Accepted: self.window().refresh_contacts() - def action_change_account(self, account_name): - def loading_progressed(value, maximum): - logging.debug("Busybar : {:} : {:}".format(value, maximum)) - self.busybar.setValue(value) - self.busybar.setMaximum(maximum) - QApplication.processEvents() + def action_change_account(self, account): if self.app.current_account: self.app.save_cache(self.app.current_account) - self.app.current_account = None + self.app.current_account = account self.refresh() self.busybar.setMinimum(0) self.busybar.setMaximum(0) self.busybar.setValue(-1) self.busybar.show() - self.status_label.setText(self.tr("Loading account {0}").format(account_name)) - self.loader.set_account_name(account_name) self.homescreen.button_new.hide() self.homescreen.button_import.hide() @@ -377,25 +384,3 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.app.stop() super().closeEvent(event) - def showEvent(self, event): - super().showEvent(event) - if not self.initialized: - # if default account in preferences... - if self.app.preferences['account'] != "": - logging.debug("Loading default account") - self.action_change_account(self.app.preferences['account']) - # no default account... - else: - # if at least one account exists, set it as default... - if len(self.app.accounts) > 0: - # capture names sorted alphabetically - names = list(self.app.accounts.keys()) - names.sort() - # set first name in list as default in preferences - self.app.preferences['account'] = names[0] - self.app.save_preferences(self.app.preferences) - # open it - logging.debug("No default account in preferences. Set %s as default account." % names[0]) - self.action_change_account(self.app.preferences['account']) - - self.initialized = True diff --git a/src/cutecoin/gui/member.py b/src/cutecoin/gui/member.py index 853195085ab25eefbe0d6a36e4e3ad6325d2feb9..1b89afa9adcde8b41583bcad016e1642314eaa6e 100644 --- a/src/cutecoin/gui/member.py +++ b/src/cutecoin/gui/member.py @@ -39,7 +39,7 @@ class MemberDialog(QDialog, Ui_DialogMember): # if selected member is not the account member... if person.pubkey != self.account.pubkey: # add path from selected member to account member - path = graph.get_shortest_path_between_members(person, self.account.get_person()) + path = graph.get_shortest_path_between_members(person, self.account.identity(self.community)) text = self.tr(""" <table cellpadding="5"> diff --git a/src/cutecoin/gui/transactions_tab.py b/src/cutecoin/gui/transactions_tab.py index 08a4c1c57acfe9b23a11241eb56a8118386d9ea3..69760d374a8d12ce194f214718c5e27362867b09 100644 --- a/src/cutecoin/gui/transactions_tab.py +++ b/src/cutecoin/gui/transactions_tab.py @@ -84,13 +84,14 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): for r in received_list: amount += r.metadata['amount'] self.progressbar.hide() - text = self.tr("Received {0} {1} from {2} transfers").format(amount, - self.community.currency, - len(received_list)) - toast.display(self.tr("New transactions received"), text) + if len(received_list) > 0: + text = self.tr("Received {0} {1} from {2} transfers").format(amount, + self.community.currency, + len(received_list)) + toast.display(self.tr("New transactions received"), text) - self.table_history.model().sourceModel().refresh_transfers() - self.table_history.resizeColumnsToContents() + self.table_history.model().sourceModel().refresh_transfers() + self.table_history.resizeColumnsToContents() def refresh_balance(self): # if referential is "units" diff --git a/src/cutecoin/gui/transfer.py b/src/cutecoin/gui/transfer.py index cc37d2e04d8df7b2dcb86a82cddc886941718381..9fe89882ed519c95e3fb16d6fa20007a614dcf82 100644 --- a/src/cutecoin/gui/transfer.py +++ b/src/cutecoin/gui/transfer.py @@ -70,17 +70,12 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): if self.password_asker.result() == QDialog.Rejected: return - try: - QApplication.setOverrideCursor(Qt.WaitCursor) - QApplication.processEvents() - self.wallet.transfer_broadcasted.connect(self.money_sent) - asyncio.async(self.wallet.send_money(self.account.salt, password, self.community, - recipient, amount, comment)) - finally: - QApplication.restoreOverrideCursor() - QApplication.processEvents() - - super().accept() + QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.processEvents() + self.wallet.transfer_broadcasted.connect(self.money_sent) + self.wallet.broadcast_error.connect(self.handle_error) + asyncio.async(self.wallet.send_money(self.account.salt, password, self.community, + recipient, amount, comment)) @pyqtSlot(str) def money_sent(self, receiver_uid):