Skip to content
Snippets Groups Projects
Commit 8d6dae1c authored by inso's avatar inso
Browse files

Fixed various errors when connection timeouts

parent 6bacfb4c
No related branches found
No related tags found
No related merge requests found
......@@ -109,7 +109,8 @@ class API(object):
- `path`: the request path
"""
response = requests.get(self.reverse_url(path), params=kwargs, headers=self.headers)
response = requests.get(self.reverse_url(path), params=kwargs,
headers=self.headers, timeout=15)
if response.status_code != 200:
raise ValueError('status code != 200 => %d (%s)' % (response.status_code, response.text))
......@@ -127,7 +128,8 @@ class API(object):
kwargs['self'] = kwargs.pop('self_')
logging.debug("POST : {0}".format(kwargs))
response = requests.post(self.reverse_url(path), data=kwargs, headers=self.headers)
response = requests.post(self.reverse_url(path), data=kwargs, headers=self.headers,
timeout=15)
if response.status_code != 200:
raise ValueError('status code != 200 => %d (%s)' % (response.status_code, response.text))
......
......@@ -18,6 +18,7 @@ import time
from .wallet import Wallet
from .community import Community
from .person import Person
from ..tools.exceptions import NoPeerAvailable
class Account(object):
......@@ -64,7 +65,7 @@ class Account(object):
communities = []
for data in json_data['communities']:
communities.append(Community.load(data))
communities.append(Community.load(data))
account = cls(salt, pubkey, name, communities, wallets, contacts)
return account
......
......@@ -10,31 +10,71 @@ from ucoinpy.documents.peer import Peer, Endpoint, BMAEndpoint
from ucoinpy.documents.block import Block
from ..tools.exceptions import NoPeerAvailable
import logging
import time
import inspect
import hashlib
from requests.exceptions import ConnectTimeout
class Cache():
def __init__(self, community):
self.latest_block = 0
self.community = community
self.data = {}
def refresh(self):
self.latest_block = self.community.current_blockid()['number']
self.data = {}
def request(self, request, req_args={}, get_args={}):
cache_key = (hash(request),
hash(tuple(frozenset(sorted(req_args.keys())))),
hash(tuple(frozenset(sorted(req_args.items())))),
hash(tuple(frozenset(sorted(get_args.keys())))),
hash(tuple(frozenset(sorted(get_args.items())))))
if cache_key not in self.data.keys():
result = self.community.request(request, req_args, get_args,
cached=False)
# Do not cache block 0
if self.latest_block == 0:
return result
else:
self.data[cache_key] = result
return self.data[cache_key]
else:
return self.data[cache_key]
class Community(object):
'''
classdocs
'''
def __init__(self, currency, peers):
'''
A community is a group of nodes using the same currency.
'''
self.currency = currency
self.peers = [p for p in peers if p.currency == currency]
self.requests_cache = {}
self.last_block = None
self.cache = Cache(self)
# After initializing the community from latest peers,
# we refresh its peers tree
logging.debug("Creating community")
found_peers = self.peering()
for p in found_peers:
if p.pubkey not in [peer.pubkey for peer in peers]:
self.peers.append(p)
try:
found_peers = self.peering()
for p in found_peers:
if p.pubkey not in [peer.pubkey for peer in peers]:
self.peers.append(p)
except NoPeerAvailable:
pass
logging.debug("{0} peers found".format(len(self.peers)))
try:
self.cache.refresh()
except NoPeerAvailable:
pass
@classmethod
def create(cls, currency, peer):
......@@ -124,76 +164,40 @@ class Community(object):
members.append(m['pubkey'])
return members
def _check_current_block(self, endpoint):
if self.last_block is None:
blockid = self.current_blockid()
self.last_block = {"request_ts": time.time(),
"number": blockid['number']}
elif self.last_block["request_ts"] + 60 < time.time():
self.last_block["request_ts"] = time.time()
blockid = self.current_blockid()
if blockid['number'] > self.last_block['number']:
self.last_block["number"] = blockid['number']
self.requests_cache = {}
def _cached_request(self, request, req_args={}, get_args={}):
for peer in self.peers:
e = next(e for e in peer.endpoints if type(e) is BMAEndpoint)
self._check_current_block(e)
try:
# Do not cache block 0
if self.last_block["number"] != 0:
cache_key = (hash(request),
hash(tuple(frozenset(sorted(req_args.keys())))),
hash(tuple(frozenset(sorted(req_args.items())))),
hash(tuple(frozenset(sorted(get_args.keys())))),
hash(tuple(frozenset(sorted(get_args.items())))))
if cache_key not in self.requests_cache.keys():
if e.server:
logging.debug("Connecting to {0}:{1}".format(e.server,
e.port))
else:
logging.debug("Connecting to {0}:{1}".format(e.ipv4,
e.port))
req = request(e.conn_handler(), **req_args)
data = req.get(**get_args)
if inspect.isgenerator(data):
cached_data = []
for d in data:
cached_data.append(d)
self.requests_cache[cache_key] = cached_data
else:
self.requests_cache[cache_key] = data
return self.requests_cache[cache_key]
else:
req = request(e.conn_handler(), **req_args)
data = req.get(**get_args)
return data
except ValueError as e:
if '502' in str(e):
continue
else:
raise
raise NoPeerAvailable(self.currency)
def request(self, request, req_args={}, get_args={}, cached=True):
if cached:
return self._cached_request(request, req_args, get_args)
return self.cache.request(request, req_args, get_args)
else:
for peer in self.peers:
for peer in self.peers.copy():
e = next(e for e in peer.endpoints if type(e) is BMAEndpoint)
try:
req = request(e.conn_handler(), **req_args)
data = req.get(**get_args)
return data
if inspect.isgenerator(data):
generated = []
for d in data:
generated.append(d)
return generated
else:
return data
except ValueError as e:
if '502' in str(e):
continue
else:
raise
raise NoPeerAvailable(self.currency)
except ConnectTimeout:
# Move the timeout peer to the end
self.peers.remove(peer)
self.peers.append(peer)
continue
except TimeoutError:
# Move the timeout peer to the end
self.peers.remove(peer)
self.peers.append(peer)
continue
raise NoPeerAvailable(self.currency, len(self.peers))
def post(self, request, req_args={}, post_args={}):
for peer in self.peers:
......@@ -207,6 +211,7 @@ class Community(object):
except:
pass
return
raise NoPeerAvailable(self.currency, len(self.peers))
def broadcast(self, request, req_args={}, post_args={}):
for peer in self.peers:
......@@ -220,6 +225,7 @@ class Community(object):
raise
except:
pass
raise NoPeerAvailable(self.currency, len(self.peers))
def jsonify_peers_list(self):
data = []
......
......@@ -9,7 +9,7 @@ from ucoinpy.api import bma
from ucoinpy.documents.block import Block
from ucoinpy.documents.transaction import InputSource, OutputSource, Transaction
from ucoinpy.key import SigningKey
from ..tools.exceptions import NotEnoughMoneyError
from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable
import logging
......@@ -83,44 +83,48 @@ class Cache():
def refresh(self, community):
current_block = 0
try:
block_data = community.request(bma.blockchain.Current)
current_block = block_data['number']
except ValueError as e:
if '404' in str(e):
current_block = 0
else:
raise
with_tx = community.request(bma.blockchain.TX)
# 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']]
for block_number in parsed_blocks:
block = community.request(bma.blockchain.Block,
req_args={'number': block_number})
signed_raw = "{0}{1}\n".format(block['raw'], block['signature'])
block_doc = Block.from_signed_raw(signed_raw)
for tx in block_doc.transactions:
in_outputs = [o for o in tx.outputs
if o.pubkey == self.wallet.pubkey]
if len(in_outputs) > 0:
self.tx_received.append(tx)
in_inputs = [i for i in tx.issuers if i == self.wallet.pubkey]
if len(in_inputs) > 0:
# remove from waiting transactions list the one which were
# validated in the blockchain
self.awaiting_tx = [awaiting for awaiting in self.awaiting_tx
if awaiting.compact() != tx.compact()]
self.tx_sent.append(tx)
if current_block > self.latest_block:
self.available_sources = self.wallet.sources(community)
try:
block_data = community.request(bma.blockchain.Current)
current_block = block_data['number']
except ValueError as e:
if '404' in str(e):
current_block = 0
else:
raise
with_tx = community.request(bma.blockchain.TX)
# 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']]
for block_number in parsed_blocks:
block = community.request(bma.blockchain.Block,
req_args={'number': block_number})
signed_raw = "{0}{1}\n".format(block['raw'], block['signature'])
block_doc = Block.from_signed_raw(signed_raw)
for tx in block_doc.transactions:
in_outputs = [o for o in tx.outputs
if o.pubkey == self.wallet.pubkey]
if len(in_outputs) > 0:
self.tx_received.append(tx)
in_inputs = [i for i in tx.issuers if i == self.wallet.pubkey]
if len(in_inputs) > 0:
# remove from waiting transactions list the one which were
# validated in the blockchain
self.awaiting_tx = [awaiting for awaiting in self.awaiting_tx
if awaiting.compact() != tx.compact()]
self.tx_sent.append(tx)
if current_block > self.latest_block:
self.available_sources = self.wallet.sources(community)
except NoPeerAvailable:
return
self.tx_sent = self.tx_sent[:50]
self.tx_received = self.tx_received[:50]
......
......@@ -3,11 +3,9 @@ Created on 24 dec. 2014
@author: inso
'''
from PyQt5.QtWidgets import QDialog, QErrorMessage, QInputDialog, QLineEdit, QMessageBox
from cutecoin.core.person import Person
from cutecoin.gen_resources.certification_uic import Ui_CertificationDialog
from PyQt5.QtWidgets import QDialog, QMessageBox
from ..tools.exceptions import NoPeerAvailable
from ..gen_resources.certification_uic import Ui_CertificationDialog
class CertificationDialog(QDialog, Ui_CertificationDialog):
......@@ -52,6 +50,10 @@ class CertificationDialog(QDialog, Ui_CertificationDialog):
QMessageBox.critical(self, "Certification",
"Something wrong happened : {0}".format(e),
QMessageBox.Ok)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Certification",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
self.accepted.emit()
self.close()
......
......@@ -14,7 +14,7 @@ from .add_contact import AddContactDialog
from .wot_tab import WotTabWidget
from .transfer import TransferMoneyDialog
from .certification import CertificationDialog
from ..tools.exceptions import PersonNotFoundError
from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable
class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
......@@ -111,6 +111,10 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
QMessageBox.critical(self, "Key not sent to community",
"Your key wasn't sent in the community. \
You can't request a membership.")
except NoPeerAvailable as e:
QMessageBox.critical(self, "Network error",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
def send_membership_leaving(self):
password = self.password_asker.ask()
......@@ -124,3 +128,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
except ValueError as e:
QMessageBox.critical(self, "Leaving demand error",
e.message)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Network error",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
......@@ -17,6 +17,7 @@ from ..models.sent import SentListModel
from ..models.received import ReceivedListModel
from ..models.wallets import WalletsListModel
from ..models.wallet import WalletListModel
from ..tools.exceptions import NoPeerAvailable
class BlockchainWatcher(QObject):
......@@ -32,16 +33,19 @@ class BlockchainWatcher(QObject):
def watch(self):
while not self.exiting:
time.sleep(10)
blockid = self.community.current_blockid()
block_number = blockid['number']
if self.last_block != block_number:
for w in self.account.wallets:
w.refresh_cache(self.community)
logging.debug("New block, {0} mined in {1}".format(block_number,
self.community.currency))
self.new_block_mined.emit(block_number)
self.last_block = block_number
try:
blockid = self.community.current_blockid()
block_number = blockid['number']
if self.last_block != block_number:
for w in self.account.wallets:
w.refresh_cache(self.community)
logging.debug("New block, {0} mined in {1}".format(block_number,
self.community.currency))
self.new_block_mined.emit(block_number)
self.last_block = block_number
except NoPeerAvailable:
return
new_block_mined = pyqtSignal(int)
......
......@@ -4,7 +4,7 @@ Created on 1 févr. 2014
@author: inso
'''
from cutecoin.gen_resources.mainwindow_uic import Ui_MainWindow
from PyQt5.QtWidgets import QMainWindow, QAction, QFileDialog, QProgressBar, QLabel
from PyQt5.QtWidgets import QMainWindow, QAction, QFileDialog, QProgressBar, QMessageBox, QLabel
from PyQt5.QtCore import QSignalMapper, QModelIndex, QObject, QThread, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QIcon
from .process_cfg_account import ProcessConfigureAccount
......@@ -14,6 +14,7 @@ from .add_contact import AddContactDialog
from .import_account import ImportAccountDialog
from .certification import CertificationDialog
from .password_asker import PasswordAskerDialog
from ..tools.exceptions import NoPeerAvailable
from ..__init__ import __version__
import logging
......@@ -109,12 +110,24 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def open_configure_account_dialog(self):
dialog = ProcessConfigureAccount(self.app, self.app.current_account)
dialog.accepted.connect(self.refresh_wallets)
dialog.accepted.connect(self.refresh_communities)
dialog.exec_()
def refresh_wallets(self):
currency_tab = self.currencies_tabwidget.currentWidget()
currency_tab.refresh_wallets()
def refresh_communities(self):
self.currencies_tabwidget.clear()
for community in self.app.current_account.communities:
tab_currency = CurrencyTabWidget(self.app, community,
self.password_asker,
self.status_label)
tab_currency.refresh()
self.currencies_tabwidget.addTab(tab_currency,
QIcon(":/icons/currency_icon"),
community.name())
def set_as_default_account(self):
self.app.default_account = self.app.current_account.name
logging.debug(self.app.current_account)
......@@ -155,13 +168,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.currencies_tabwidget.clear()
for community in self.app.current_account.communities:
tab_currency = CurrencyTabWidget(self.app, community,
self.password_asker,
self.status_label)
tab_currency.refresh()
self.currencies_tabwidget.addTab(tab_currency,
QIcon(":/icons/currency_icon"),
community.name())
try:
tab_currency = CurrencyTabWidget(self.app, community,
self.password_asker,
self.status_label)
tab_currency.refresh()
self.currencies_tabwidget.addTab(tab_currency,
QIcon(":/icons/currency_icon"),
community.name())
except NoPeerAvailable as e:
QMessageBox.critical(self, "Could not join {0}".format(community.currency),
str(e),
QMessageBox.Ok)
continue
self.menu_contacts_list.clear()
for contact in self.app.current_account.contacts:
......
......@@ -10,9 +10,9 @@ from ..gen_resources.account_cfg_uic import Ui_AccountConfigurationDialog
from ..gui.process_cfg_community import ProcessConfigureCommunity
from ..gui.password_asker import PasswordAskerDialog
from ..models.communities import CommunitiesListModel
from ..tools.exceptions import KeyAlreadyUsed, Error
from ..tools.exceptions import KeyAlreadyUsed, Error, NoPeerAvailable
from PyQt5.QtWidgets import QDialog, QErrorMessage, QInputDialog, QMessageBox, QLineEdit
from PyQt5.QtWidgets import QDialog, QMessageBox
class Step():
......@@ -191,7 +191,13 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog):
def open_process_edit_community(self, index):
community = self.account.communities[index.row()]
dialog = ProcessConfigureCommunity(self.account, community, self.password_asker)
try:
dialog = ProcessConfigureCommunity(self.account, community, self.password_asker)
except NoPeerAvailable as e:
QMessageBox.critical(self, "Error",
str(e), QMessageBox.Ok)
return
dialog.accepted.connect(self.action_edit_community)
dialog.exec_()
......@@ -205,7 +211,8 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog):
self.stacked_pages.setCurrentIndex(next_index)
self.step.display_page()
except Error as e:
QErrorMessage(self).showMessage(e.message)
QMessageBox.critical(self, "Error",
str(e), QMessageBox.Ok)
else:
self.accept()
......@@ -223,7 +230,8 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog):
try:
self.app.add_account(self.account)
except KeyAlreadyUsed as e:
QErrorMessage(self).showMessage(e.message)
QMessageBox.critical(self, "Error",
str(e), QMessageBox.Ok)
password = self.edit_password.text()
else:
password = self.password_asker.ask()
......
......@@ -3,10 +3,9 @@ Created on 2 févr. 2014
@author: inso
'''
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
from PyQt5.QtWidgets import QDialog, QMessageBox
from ..tools.exceptions import NotEnoughMoneyError
from ..core.person import Person
from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable
from ..gen_resources.transfer_uic import Ui_TransferMoneyDialog
import logging
......@@ -75,7 +74,10 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog):
QMessageBox.critical(self, "Money transfer",
"You don't have enough money available in this block : \n{0}"
.format(e.message))
except NoPeerAvailable as e:
QMessageBox.critical(self, "Money transfer",
"Couldn't connect to network : {0}".format(e),
QMessageBox.Ok)
self.accepted.emit()
self.close()
......
......@@ -8,6 +8,7 @@ from ucoinpy.api import bma
from ucoinpy.documents.peer import BMAEndpoint, Peer
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
from .peer import PeerItem, RootItem
from requests.exceptions import ConnectTimeout
import logging
......@@ -104,10 +105,16 @@ class PeeringTreeModel(QAbstractItemModel):
try:
e = next((e for e in peer.endpoints if type(e) is BMAEndpoint))
peers = bma.network.peering.Peers(e.conn_handler()).get()
for peer_data in peers:
peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['value']['raw'],
peer_data['value']['signature']))
child_node_item = PeerItem(peer, peer_item)
peer_item.appendChild(child_node_item)
try:
for peer_data in peers:
peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['value']['raw'],
peer_data['value']['signature']))
child_node_item = PeerItem(peer, peer_item)
peer_item.appendChild(child_node_item)
except ConnectTimeout:
continue
except TimeoutError:
continue
except StopIteration as e:
continue
......@@ -13,6 +13,9 @@ class Error(Exception):
'''
self.message = "Error : " + message
def __str__(self):
return self.message
class NotMemberOfCommunityError(Error):
......@@ -137,10 +140,10 @@ class NoPeerAvailable(Error):
Exception raised when a community doesn't have any
peer available.
'''
def __init__(self, currency):
def __init__(self, currency, peers):
'''
Constructor
'''
super() .__init__(
"No peer found in {0} community"
.format(currency))
"No peer answered in {0} community ({1} peers available)"
.format(currency, peers))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment