From 8d6dae1ccc942a6435dba819d1857156f600379b Mon Sep 17 00:00:00 2001
From: Inso <insomniak.fr@gmail.com>
Date: Thu, 22 Jan 2015 20:57:15 +0100
Subject: [PATCH] Fixed various errors when connection timeouts

---
 lib/ucoinpy/api/bma/__init__.py         |   6 +-
 src/cutecoin/core/account.py            |   3 +-
 src/cutecoin/core/community.py          | 136 +++++++++++++-----------
 src/cutecoin/core/wallet.py             |  82 +++++++-------
 src/cutecoin/gui/certification.py       |  12 ++-
 src/cutecoin/gui/community_tab.py       |  10 +-
 src/cutecoin/gui/currency_tab.py        |  24 +++--
 src/cutecoin/gui/mainwindow.py          |  35 ++++--
 src/cutecoin/gui/process_cfg_account.py |  18 +++-
 src/cutecoin/gui/transfer.py            |  10 +-
 src/cutecoin/models/peering.py          |  17 ++-
 src/cutecoin/tools/exceptions.py        |   9 +-
 12 files changed, 214 insertions(+), 148 deletions(-)

diff --git a/lib/ucoinpy/api/bma/__init__.py b/lib/ucoinpy/api/bma/__init__.py
index 02fac8a9..768bd447 100644
--- a/lib/ucoinpy/api/bma/__init__.py
+++ b/lib/ucoinpy/api/bma/__init__.py
@@ -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))
diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py
index cfc93f61..984b7adf 100644
--- a/src/cutecoin/core/account.py
+++ b/src/cutecoin/core/account.py
@@ -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
diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py
index c43f5fab..3b05d30c 100644
--- a/src/cutecoin/core/community.py
+++ b/src/cutecoin/core/community.py
@@ -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 = []
diff --git a/src/cutecoin/core/wallet.py b/src/cutecoin/core/wallet.py
index 3c48bf67..64e788f1 100644
--- a/src/cutecoin/core/wallet.py
+++ b/src/cutecoin/core/wallet.py
@@ -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]
diff --git a/src/cutecoin/gui/certification.py b/src/cutecoin/gui/certification.py
index 6d87765d..05c61bf0 100644
--- a/src/cutecoin/gui/certification.py
+++ b/src/cutecoin/gui/certification.py
@@ -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()
diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py
index c6c79275..7e35e148 100644
--- a/src/cutecoin/gui/community_tab.py
+++ b/src/cutecoin/gui/community_tab.py
@@ -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)
diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py
index c305f4e1..6a626f1a 100644
--- a/src/cutecoin/gui/currency_tab.py
+++ b/src/cutecoin/gui/currency_tab.py
@@ -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)
 
diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py
index 9d2e6945..8f7545dc 100644
--- a/src/cutecoin/gui/mainwindow.py
+++ b/src/cutecoin/gui/mainwindow.py
@@ -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:
diff --git a/src/cutecoin/gui/process_cfg_account.py b/src/cutecoin/gui/process_cfg_account.py
index 3fa823e7..a2dd2ff3 100644
--- a/src/cutecoin/gui/process_cfg_account.py
+++ b/src/cutecoin/gui/process_cfg_account.py
@@ -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()
diff --git a/src/cutecoin/gui/transfer.py b/src/cutecoin/gui/transfer.py
index c903e5ed..e95968f6 100644
--- a/src/cutecoin/gui/transfer.py
+++ b/src/cutecoin/gui/transfer.py
@@ -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()
 
diff --git a/src/cutecoin/models/peering.py b/src/cutecoin/models/peering.py
index f757f771..dffe9639 100644
--- a/src/cutecoin/models/peering.py
+++ b/src/cutecoin/models/peering.py
@@ -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
diff --git a/src/cutecoin/tools/exceptions.py b/src/cutecoin/tools/exceptions.py
index 2397d91d..27317ae6 100644
--- a/src/cutecoin/tools/exceptions.py
+++ b/src/cutecoin/tools/exceptions.py
@@ -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))
-- 
GitLab