diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index 9b43b8f378731f0382f6d74f520d1fedfbb91f36..35bb49181f3623346d6aca6b2e982858cefea9b5 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -168,7 +168,7 @@ class Account(QObject): maximums = {} def progressing(value, maximum, hash): - logging.debug("Loading = {0} : {1} : {2}".format(value, maximum, loaded_wallets)) + #logging.debug("Loading = {0} : {1} : {2}".format(value, maximum, loaded_wallets)) values[hash] = value maximums[hash] = maximum account_value = sum(values.values()) diff --git a/src/cutecoin/core/net/network.py b/src/cutecoin/core/net/network.py index 4cbd8c10617763df2e36fb88712a8ebff870785b..62b26dcf1689041346cfaf8a09f058aae2d409cf 100644 --- a/src/cutecoin/core/net/network.py +++ b/src/cutecoin/core/net/network.py @@ -165,9 +165,10 @@ class Network(QObject): Get the latest block considered valid It is the most frequent last block of every known nodes """ - blocks = [n.block['number'] for n in self.synced_nodes] - if len(blocks) > 0: - return max(set(blocks), key=blocks.count) + blocks_numbers = [n.block['number'] for n in self.synced_nodes + if n.block != qtbma.blockchain.Block.null_value] + if len(blocks_numbers) > 0: + return blocks_numbers[0] else: return 0 @@ -177,9 +178,10 @@ class Network(QObject): Get the latest block considered valid It is the most frequent last block of every known nodes """ - blocks = [n.block['hash'] for n in self.synced_nodes if n.block != qtbma.blockchain.Block.null_value] - if len(blocks) > 0: - return max(set(blocks), key=blocks.count) + blocks_hash = [n.block['hash'] for n in self.synced_nodes + if n.block != qtbma.blockchain.Block.null_value] + if len(blocks_hash) > 0: + return blocks_hash[0] else: return Block.Empty_Hash @@ -333,4 +335,5 @@ class Network(QObject): if self._block_found != self.latest_block_hash and node.state == Node.ONLINE: logging.debug("Latest block changed : {0}".format(self.latest_block_number)) self._block_found = self.latest_block_hash + # Do not emit block change for empty block self.new_block_mined.emit(self.latest_block_number) diff --git a/src/cutecoin/core/txhistory.py b/src/cutecoin/core/txhistory.py index b958c878a3f10bfe96abe8c5f8fff83b212aa873..6105ac21ca128a912199a21318cce90a7df08fb9 100644 --- a/src/cutecoin/core/txhistory.py +++ b/src/cutecoin/core/txhistory.py @@ -1,7 +1,9 @@ import asyncio import logging +import hashlib from .transfer import Transfer from ucoinpy.documents.transaction import InputSource, OutputSource +from ucoinpy.documents.block import Block from ..tools.exceptions import LookupFailureError from .net.api import bma as qtbma @@ -81,21 +83,19 @@ class TxHistory(): return state @asyncio.coroutine - def _parse_transaction(self, community, txdata, received_list, txid, current_block): - 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]] + def _parse_transaction(self, community, tx, block_number, + mediantime, received_list, + current_block, txid): + receivers = [o.pubkey for o in tx.outputs + if o.pubkey != tx.issuers[0]] - block_number = txdata['block_number'] - - mediantime = txdata['time'] state = yield from TxHistory._validation_state(community, block_number, current_block) if len(receivers) == 0: - receivers = [txdata['issuers'][0]] + receivers = [tx.issuers[0]] try: - issuer = yield from self.wallet._identities_registry.future_find(txdata['issuers'][0], community) + issuer = yield from self.wallet._identities_registry.future_find(tx.issuers[0], community) issuer_uid = issuer.uid except LookupFailureError: issuer_uid = "" @@ -108,57 +108,96 @@ class TxHistory(): metadata = {'block': block_number, 'time': mediantime, - 'comment': txdata['comment'], - 'issuer': txdata['issuers'][0], + 'comment': tx.comment, + 'issuer': tx.issuers[0], 'issuer_uid': issuer_uid, 'receiver': receivers[0], 'receiver_uid': receiver_uid, 'txid': txid} - in_issuers = len([i for i in txdata['issuers'] + in_issuers = len([i for i in tx.issuers if i == self.wallet.pubkey]) > 0 - in_outputs = len([o for o in tx_outputs + in_outputs = len([o for o in tx.outputs if o.pubkey == self.wallet.pubkey]) > 0 awaiting = [t for t in self._transfers if t.state in (Transfer.AWAITING, Transfer.VALIDATING)] # We check if the transaction correspond to one we sent # but not from this cutecoin Instance - if txdata['hash'] not in [t.hash for t in awaiting]: + tx_hash = hashlib.sha1(tx.signed_raw().encode("ascii")).hexdigest().upper() + if tx_hash not in [t.hash for t in awaiting]: # 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 tx.outputs if o.pubkey != self.wallet.pubkey] amount = 0 for o in outputs: amount += o.amount metadata['amount'] = amount - transfer = Transfer.create_from_blockchain(txdata['hash'], + transfer = Transfer.create_from_blockchain(tx_hash, state, metadata.copy()) return 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 tx.outputs if o.pubkey == self.wallet.pubkey] amount = 0 for o in outputs: amount += o.amount metadata['amount'] = amount - if txdata['hash'] not in [t.hash for t in awaiting]: - transfer = Transfer.create_from_blockchain(txdata['hash'], + if tx_hash not in [t.hash for t in awaiting]: + transfer = Transfer.create_from_blockchain(tx_hash, state, metadata.copy()) received_list.append(transfer) return transfer else: - transfer = [t for t in awaiting if t.hash == txdata['hash']][0] - transfer.check_registered(txdata['hash'], current_block['number'], mediantime, + transfer = [t for t in awaiting if t.hash == tx_hash][0] + + transfer.check_registered(tx_hash, current_block['number'], mediantime, community.network.fork_window(community.members_pubkeys()) + 1) return None + @asyncio.coroutine + def _parse_block(self, community, block_number, received_list, current_block, txmax): + block = yield from community.bma_access.future_request(qtbma.blockchain.Block, + req_args={'number': block_number}) + signed_raw = "{0}{1}\n".format(block['raw'], + block['signature']) + transfers = [] + 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): + transfer = yield from self._parse_transaction(community, tx, block_number, + block_doc.mediantime, received_list, + current_block, txid+txmax) + if transfer != None: + logging.debug("Transfer amount : {0}".format(transfer.metadata['amount'])) + transfers.append(transfer) + else: + logging.debug("None transfer") + return transfers + + @asyncio.coroutine + def request_dividends(self, community, parsed_block): + dividends_data = qtbma.ud.History.null_value + for i in range(0, 6): + if dividends_data == qtbma.ud.History.null_value: + dividends_data = yield from community.bma_access.future_request(qtbma.ud.History, + req_args={'pubkey': self.wallet.pubkey}) + + dividends = dividends_data['history']['history'] + for d in dividends: + if d['block_number'] < parsed_block: + dividends.remove(d) + return dividends + @asyncio.coroutine def refresh(self, community, received_list): """ @@ -178,40 +217,17 @@ class TxHistory(): [max(0, self.latest_block - community.network.fork_window(members_pubkeys))] parsed_block = min(set(blocks)) logging.debug("Refresh from : {0} to {1}".format(self.latest_block, current_block['number'])) - dividends_data = qtbma.ud.History.null_value - for i in range(0, 6): - if dividends_data == qtbma.ud.History.null_value: - if i == 5: - return - dividends_data = yield from community.bma_access.future_request(qtbma.ud.History, - req_args={'pubkey': self.wallet.pubkey}) - - dividends = dividends_data['history']['history'] - for d in dividends: - if d['block_number'] < parsed_block: - dividends.remove(d) - + dividends = yield from self.request_dividends(community, parsed_block) + with_tx_data = yield from community.bma_access.future_request(qtbma.blockchain.TX) + blocks_with_tx = with_tx_data['result']['blocks'] new_transfers = [] new_dividends = [] # 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['number']: - tx_history = qtbma.tx.history.Blocks.null_value - for i in range(0, 6): - if tx_history == qtbma.tx.history.Blocks.null_value: - if i == 5: - return - - 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 + 99)}) - if self._stop_coroutines: - return - udid = 0 - for d in [ud for ud in dividends if ud['block_number'] in range(parsed_block, parsed_block+100)]: + for d in [ud for ud in dividends if ud['block_number'] == parsed_block]: state = yield from TxHistory._validation_state(community, d['block_number'], current_block) if d['block_number'] not in [ud['block_number'] for ud in self._dividends]: @@ -225,22 +241,14 @@ class TxHistory(): known_dividend['state'] = state # We parse only blocks with transactions - transactions = tx_history['history']['received'] + tx_history['history']['sent'] - for (txid, txdata) in enumerate(transactions): - if self._stop_coroutines: - return - if len(txdata['issuers']) == 0: - logging.debug("Error with : {0}, from {1} to {2}".format(self.wallet.pubkey, - parsed_block, - current_block['number'])) - else: - transfer = yield from self._parse_transaction(community, txdata, received_list, - udid + txid, current_block) - if transfer: - new_transfers.append(transfer) + if parsed_block in blocks_with_tx: + transfers = yield from self._parse_block(community, parsed_block, + received_list, current_block, + udid + len(new_transfers)) + new_transfers += transfers self.wallet.refresh_progressed.emit(parsed_block, current_block['number'], self.wallet.pubkey) - parsed_block += 100 + parsed_block += 1 if current_block['number'] > self.latest_block: self.available_sources = yield from self.wallet.future_sources(community) diff --git a/src/cutecoin/core/txhistory_indexation.py b/src/cutecoin/core/txhistory_indexation.py new file mode 100644 index 0000000000000000000000000000000000000000..399f1f064bf7f5a385b6f156a55dae6fce5b34de --- /dev/null +++ b/src/cutecoin/core/txhistory_indexation.py @@ -0,0 +1,255 @@ +import asyncio +import logging +from .transfer import Transfer +from ucoinpy.documents.transaction import InputSource, OutputSource +from ..tools.exceptions import LookupFailureError +from .net.api import bma as qtbma + + +class TxHistory(): + def __init__(self, app, wallet): + self._latest_block = 0 + self.wallet = wallet + self.app = app + self._stop_coroutines = False + + self._transfers = [] + self.available_sources = [] + self._dividends = [] + + @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: + self._transfers.append(Transfer.load(s)) + + for s in data['sources']: + self.available_sources.append(InputSource.from_inline(s['inline'])) + + for d in data['dividends']: + self._dividends.append(d) + + 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())}) + + data_dividends = [] + for d in self._dividends: + data_dividends.append(d) + + return {'latest_block': self.latest_block, + 'transfers': data_transfer, + 'sources': data_sources, + 'dividends': data_dividends} + + @property + def transfers(self): + return [t for t in self._transfers if t.state != Transfer.DROPPED] + + @property + def dividends(self): + return self._dividends.copy() + + def stop_coroutines(self): + self._stop_coroutines = True + + @staticmethod + @asyncio.coroutine + def _validation_state(community, block_number, current_block): + members_pubkeys = yield from community.members_pubkeys() + if block_number + community.network.fork_window(members_pubkeys) + 1 < current_block["number"]: + state = Transfer.VALIDATED + else: + state = Transfer.VALIDATING + return state + + @asyncio.coroutine + def _parse_transaction(self, community, txdata, received_list, txid, current_block): + 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'] + state = yield from TxHistory._validation_state(community, block_number, current_block) + + if len(receivers) == 0: + receivers = [txdata['issuers'][0]] + + try: + issuer = yield from self.wallet._identities_registry.future_find(txdata['issuers'][0], community) + issuer_uid = issuer.uid + except LookupFailureError: + issuer_uid = "" + + try: + receiver = yield from self.wallet._identities_registry.future_find(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 + awaiting = [t for t in self._transfers + if t.state in (Transfer.AWAITING, Transfer.VALIDATING)] + + # We check if the transaction correspond to one we sent + # but not from this cutecoin Instance + if txdata['hash'] not in [t.hash for t in awaiting]: + # 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 + transfer = Transfer.create_from_blockchain(txdata['hash'], + state, + metadata.copy()) + return 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 + + if txdata['hash'] not in [t.hash for t in awaiting]: + transfer = Transfer.create_from_blockchain(txdata['hash'], + state, + metadata.copy()) + received_list.append(transfer) + return transfer + else: + transfer = [t for t in awaiting if t.hash == txdata['hash']][0] + transfer.check_registered(txdata['hash'], current_block['number'], mediantime, + community.network.fork_window(community.members_pubkeys()) + 1) + + @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 + """ + current_block = yield from community.bma_access.future_request(qtbma.blockchain.Block, + req_args={'number': community.network.latest_block_number}) + members_pubkeys = yield from community.members_pubkeys() + # We look for the first block to parse, depending on awaiting and validating transfers and ud... + blocks = [tx.metadata['block'] for tx in self._transfers + if tx.state in (Transfer.AWAITING, Transfer.VALIDATING)] +\ + [ud['block_number'] for ud in self._dividends + if ud['state'] in (Transfer.AWAITING, Transfer.VALIDATING)] +\ + [max(0, self.latest_block - community.network.fork_window(members_pubkeys))] + parsed_block = min(set(blocks)) + logging.debug("Refresh from : {0} to {1}".format(self.latest_block, current_block['number'])) + dividends_data = qtbma.ud.History.null_value + for i in range(0, 6): + if dividends_data == qtbma.ud.History.null_value: + dividends_data = yield from community.bma_access.future_request(qtbma.ud.History, + req_args={'pubkey': self.wallet.pubkey}) + + dividends = dividends_data['history']['history'] + for d in dividends: + if d['block_number'] < parsed_block: + dividends.remove(d) + + new_transfers = [] + new_dividends = [] + # 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['number']: + udid = 0 + for d in [ud for ud in dividends if ud['block_number'] in range(parsed_block, parsed_block+100)]: + state = yield from TxHistory._validation_state(community, d['block_number'], current_block) + + if d['block_number'] not in [ud['block_number'] for ud in self._dividends]: + d['id'] = udid + d['state'] = state + new_dividends.append(d) + udid += 1 + else: + known_dividend = [ud for ud in self._dividends + if ud['block_number'] == d['block_number']][0] + known_dividend['state'] = state + + tx_history = qtbma.tx.history.Blocks.null_value + for i in range(0, 6): + if tx_history == qtbma.tx.history.Blocks.null_value: + 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 + 99)}) + # If after 6 requests we still get wrong data + # we continue to next loop + if tx_history == qtbma.tx.history.Blocks.null_value: + continue + + # We parse only blocks with transactions + transactions = tx_history['history']['received'] + tx_history['history']['sent'] + for (txid, txdata) in enumerate(transactions): + if self._stop_coroutines: + return + if len(txdata['issuers']) == 0: + logging.debug("Error with : {0}, from {1} to {2}".format(self.wallet.pubkey, + parsed_block, + current_block['number'])) + else: + transfer = yield from self._parse_transaction(community, txdata, received_list, + udid + txid, current_block) + if transfer: + new_transfers.append(transfer) + + self.wallet.refresh_progressed.emit(parsed_block, current_block['number'], self.wallet.pubkey) + parsed_block += 100 + + if current_block['number'] > self.latest_block: + self.available_sources = yield from self.wallet.future_sources(community) + if self._stop_coroutines: + return + self.latest_block = current_block['number'] + + for transfer in awaiting: + transfer.check_refused(current_block['medianTime'], + community.parameters['avgGenTime'], + community.parameters['medianTimeBlocks']) + + self._transfers = self._transfers + new_transfers + self._dividends = self._dividends + new_dividends + + self.wallet.refresh_finished.emit(received_list) diff --git a/src/cutecoin/gui/community_view.py b/src/cutecoin/gui/community_view.py index f3843685f992e21ec1368b76276bd68d795d1935..b012c3abef3a54417bd155014e2ff8ed26f690ca 100644 --- a/src/cutecoin/gui/community_view.py +++ b/src/cutecoin/gui/community_view.py @@ -164,10 +164,9 @@ class CommunityWidget(QWidget, Ui_CommunityWidget): def refresh_data(self): """ - Refresh data when the blockchain watcher finished handling datas + Refresh data """ self.tab_history.refresh_balance() - self.tab_identities.refresh_data() self.refresh_status() @asyncify diff --git a/src/cutecoin/models/txhistory.py b/src/cutecoin/models/txhistory.py index e37faf30fb9b07fd99e38976fc0016a2d0db1ed3..27e0e71d4c4ba9b6a29bb0b8d49565a1bf1aeef1 100644 --- a/src/cutecoin/models/txhistory.py +++ b/src/cutecoin/models/txhistory.py @@ -212,7 +212,6 @@ class HistoryTableModel(QAbstractTableModel): def account(self): return self.app.current_account - @property def transfers(self): return self.account.transfers(self.community) + self.account.dividends(self.community) @@ -279,7 +278,7 @@ class HistoryTableModel(QAbstractTableModel): def refresh_transfers(self): self.beginResetModel() self.transfers_data = [] - for transfer in self.transfers: + for transfer in self.transfers(): data = None if type(transfer) is Transfer: if transfer.metadata['issuer'] == self.account.pubkey: