From 7a59afbab7e8c63e6336340728955d1a6ccce1da Mon Sep 17 00:00:00 2001
From: vtexier <vit@free.fr>
Date: Sun, 16 Feb 2020 18:41:20 +0100
Subject: [PATCH] [enh] update duniterpy calls to version 0.56.0

---
 requirements.txt                              |   2 +-
 src/sakia/data/connectors/bma.py              |  45 ++----
 src/sakia/data/connectors/node.py             | 153 +++++++++++-------
 src/sakia/data/entities/connection.py         |   8 +-
 src/sakia/data/entities/node.py               |   8 +-
 src/sakia/data/processors/blockchain.py       |   4 +-
 src/sakia/data/processors/nodes.py            |   2 +-
 src/sakia/gui/dialogs/connection_cfg/view.py  |   3 +-
 src/sakia/gui/dialogs/contact/view.py         |   4 +-
 src/sakia/gui/dialogs/revocation/model.py     |   3 +-
 .../gui/navigation/network/controller.py      |   2 +-
 .../gui/navigation/network/table_model.py     |   2 +-
 src/sakia/gui/sub/transfer/controller.py      |   4 +-
 src/sakia/gui/widgets/context_menu.py         |   4 +-
 src/sakia/services/network.py                 |   3 +-
 15 files changed, 127 insertions(+), 120 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index a522e088..3af2d740 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@ aiohttp==3.6.2
 async-timeout==3.0.1
 asynctest==0.13.0
 attrs==19.3.0
-duniterpy==0.55.1
+duniterpy==0.56.0
 jsonschema==3.2.0
 networkx==2.4
 PyQt5==5.9.2
diff --git a/src/sakia/data/connectors/bma.py b/src/sakia/data/connectors/bma.py
index 6cbfc6ec..57ff5b43 100644
--- a/src/sakia/data/connectors/bma.py
+++ b/src/sakia/data/connectors/bma.py
@@ -1,8 +1,9 @@
 import logging
 import aiohttp
 from aiohttp import ClientError
-from duniterpy.api import bma, errors
-from duniterpy.documents import BMAEndpoint, SecuredBMAEndpoint
+from duniterpy.api import client, bma, errors
+from duniterpy.api.endpoint import BMAEndpoint, SecuredBMAEndpoint
+from duniterpy.api.client import parse_error
 from sakia.errors import NoPeerAvailable
 from pkg_resources import parse_version
 from socket import gaierror
@@ -25,7 +26,7 @@ async def parse_responses(responses):
                     try:
                         result = (
                             False,
-                            errors.DuniterError(bma.parse_error(error)).message,
+                            errors.DuniterError(parse_error(error)).message,
                         )
                     except jsonschema.ValidationError:
                         result = (False, error)
@@ -197,7 +198,6 @@ class BmaConnector:
         nb_verification = min(max(1, 0.66 * len(synced_nodes)), 3)
         # We try to find agreeing nodes from one 1 to 66% of nodes, max 10
         session = aiohttp.ClientSession()
-        filtered_data = {}
         try:
             while (
                 max([len(nodes) for nodes in answers.values()] + [0]) <= nb_verification
@@ -216,31 +216,19 @@ class BmaConnector:
                                 str(request.__name__), str(endpoint)
                             )
                         )
+                        # create client
+                        _client = client.Client(endpoint, session, proxy=self._user_parameters.proxy())
                         futures.append(
                             self._verified_request(
                                 node,
-                                request(
-                                    next(
-                                        endpoint.conn_handler(
-                                            session, proxy=self._user_parameters.proxy()
-                                        )
-                                    ),
-                                    **req_args
-                                ),
+                                _client(request,  **req_args)
                             )
                         )
                     if random_offline_node:
                         futures.append(
                             self._verified_request(
                                 random_offline_node[0],
-                                request(
-                                    next(
-                                        endpoint.conn_handler(
-                                            session, proxy=self._user_parameters.proxy()
-                                        )
-                                    ),
-                                    **req_args
-                                ),
+                                _client(request,  **req_args)
                             )
                         )
                 except StopIteration:
@@ -297,11 +285,8 @@ class BmaConnector:
                         str(request.__name__), str(endpoint)
                     )
                 )
-                async with aiohttp.ClientSession() as session:
-                    json_data = await request(
-                        next(endpoint.conn_handler(session), **req_args)
-                    )
-                    return json_data
+                _client = client.Client(endpoint, proxy=self._user_parameters.proxy())
+                return await _client(request, **req_args)
             except errors.DuniterError as e:
                 if e.ucode == errors.HTTP_LIMITATION:
                     self._logger.debug(str(e))
@@ -363,15 +348,9 @@ class BmaConnector:
             async with aiohttp.ClientSession() as session:
                 for endpoint in endpoints:
                     self._logger.debug("Trying to connect to : " + str(endpoint))
+                    _client = client.Client(endpoint, proxy=self._user_parameters.proxy())
                     reply = asyncio.ensure_future(
-                        request(
-                            next(
-                                endpoint.conn_handler(
-                                    session, proxy=self._user_parameters.proxy()
-                                )
-                            ),
-                            **req_args
-                        )
+                        _client(request, **req_args)
                     )
                     replies.append(reply)
 
diff --git a/src/sakia/data/connectors/node.py b/src/sakia/data/connectors/node.py
index bc33cacc..0b26e82a 100644
--- a/src/sakia/data/connectors/node.py
+++ b/src/sakia/data/connectors/node.py
@@ -1,8 +1,10 @@
 import asyncio
 import logging
 import time
+import re
 from asyncio import TimeoutError
 from socket import gaierror
+from typing import Union
 
 import aiohttp
 import jsonschema
@@ -10,8 +12,11 @@ from PyQt5.QtCore import QObject, pyqtSignal
 from aiohttp import ClientError
 
 from duniterpy.api import bma, errors
-from duniterpy.documents import BlockUID, MalformedDocumentError, BMAEndpoint
-from duniterpy.documents.peer import Peer, ConnectionHandler
+from duniterpy.api.client import Client
+from duniterpy.constants import HOST_REGEX, IPV4_REGEX, IPV6_REGEX
+from duniterpy.api.endpoint import BMAEndpoint, SecuredBMAEndpoint
+from duniterpy.documents import BlockUID, MalformedDocumentError
+from duniterpy.documents.peer import Peer
 from sakia.decorators import asyncify
 from sakia.errors import InvalidNodeCurrency
 from ..entities.node import Node
@@ -49,6 +54,8 @@ class NodeConnector(QObject):
         self._ws_tasks = {"block": None, "peer": None}
         self._connected = {"block": False, "peer": False}
         self._user_parameters = user_parameters
+        if not session:
+            session = aiohttp.ClientSession()
         self.session = session
         self._raw_logger = logging.getLogger("sakia")
         self._logger = NodeConnectorLoggerAdapter(
@@ -72,20 +79,11 @@ class NodeConnector(QObject):
         :return: A new node
         :rtype: sakia.core.net.Node
         """
-        http_scheme = "https" if secured else "http"
-        ws_scheme = "ws" if secured else "wss"
-        session = aiohttp.ClientSession()
-        peer_data = await bma.network.peering(
-            ConnectionHandler(
-                http_scheme,
-                ws_scheme,
-                address,
-                port,
-                "",
-                proxy=user_parameters.proxy(),
-                session=session,
-            )
-        )
+        endpoint = get_bma_endpoint_from_server_address(address, port, secured)
+        # Create Client from endpoint string in Duniter format
+        client = Client(endpoint, proxy=user_parameters.proxy())
+
+        peer_data = client(bma.network.peering)
 
         peer = Peer.from_signed_raw(
             "{0}{1}\n".format(peer_data["raw"], peer_data["signature"])
@@ -103,12 +101,13 @@ class NodeConnector(QObject):
         )
         logging.getLogger("sakia").debug("Node from address : {:}".format(str(node)))
 
-        return cls(node, user_parameters, session=session)
+        return cls(node, user_parameters)
 
     @classmethod
     def from_peer(cls, currency, peer, user_parameters):
         """
         Factory method to get a node from a peer document.
+
         :param str currency: The node currency. None if we don't know\
          the currency it should have, for example if its the first one we add
         :param peer: The peer document
@@ -132,8 +131,8 @@ class NodeConnector(QObject):
 
     async def safe_request(self, endpoint, request, proxy, req_args={}):
         try:
-            conn_handler = next(endpoint.conn_handler(self.session, proxy=proxy))
-            data = await request(conn_handler, **req_args)
+            client = Client(endpoint, self.session, proxy)
+            data = await client(request, **req_args)
             return data
         except errors.DuniterError as e:
             if e.ucode == 1006:
@@ -207,28 +206,30 @@ class NodeConnector(QObject):
         for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
             if not self._connected["block"]:
                 try:
-                    conn_handler = next(
-                        endpoint.conn_handler(
-                            self.session, proxy=self._user_parameters.proxy()
-                        )
-                    )
-                    ws_connection = bma.ws.block(conn_handler)
-                    async with ws_connection as ws:
-                        self._connected["block"] = True
-                        self._logger.debug("Connected successfully to block ws")
-                        async for msg in ws:
-                            if msg.type == aiohttp.WSMsgType.TEXT:
-                                self._logger.debug("Received a block")
-                                block_data = bma.parse_text(
-                                    msg.data, bma.ws.WS_BLOCk_SCHEMA
-                                )
-                                self.block_found.emit(
-                                    BlockUID(block_data["number"], block_data["hash"])
-                                )
-                            elif msg.type == aiohttp.WSMsgType.CLOSED:
-                                break
-                            elif msg.type == aiohttp.WSMsgType.ERROR:
-                                break
+                    client = Client(endpoint, self.session, self._user_parameters.proxy())
+
+                    # Create Web Socket connection on block path (async method)
+                    ws = await client(bma.ws.block)  # Type: WSConnection
+                    self._connected["block"] = True
+                    self._logger.debug("Connected successfully to block ws")
+
+                    loop = True
+                    # Iterate on each message received...
+                    while loop:
+                        # Wait and capture next message
+                        try:
+                            block_data = await ws.receive_json()
+                            jsonschema.validate(block_data, bma.ws.WS_BLOCK_SCHEMA)
+                            self._logger.debug("Received a block")
+                            self.block_found.emit(
+                                BlockUID(block_data["number"], block_data["hash"])
+                            )
+                        except TypeError as exception:
+                            self._logger.debug(exception)
+
+                    # Close session
+                    await client.close()
+
                 except (aiohttp.WSServerHandshakeError, ValueError) as e:
                     self._logger.debug(
                         "Websocket block {0} : {1}".format(type(e).__name__, str(e))
@@ -264,26 +265,27 @@ class NodeConnector(QObject):
         for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
             if not self._connected["peer"]:
                 try:
-                    conn_handler = next(
-                        endpoint.conn_handler(
-                            self.session, proxy=self._user_parameters.proxy()
-                        )
-                    )
-                    ws_connection = bma.ws.peer(conn_handler)
-                    async with ws_connection as ws:
-                        self._connected["peer"] = True
-                        self._logger.debug("Connected successfully to peer ws")
-                        async for msg in ws:
-                            if msg.type == aiohttp.WSMsgType.TEXT:
-                                self._logger.debug("Received a peer")
-                                peer_data = bma.parse_text(
-                                    msg.data, bma.ws.WS_PEER_SCHEMA
-                                )
-                                self.refresh_peer_data(peer_data)
-                            elif msg.type == aiohttp.WSMsgType.CLOSED:
-                                break
-                            elif msg.type == aiohttp.WSMsgType.ERROR:
-                                break
+                    client = Client(endpoint, self.session, self._user_parameters.proxy())
+
+                    # Create Web Socket connection on peer path (async method)
+                    ws = await client(bma.ws.peer)  # Type: WSConnection
+                    self._connected["peer"] = True
+                    self._logger.debug("Connected successfully to peer ws")
+
+                    loop = True
+                    # Iterate on each message received...
+                    while loop:
+                        try:
+                            # Wait and capture next message
+                            peer_data = await ws.receive_json()
+                            jsonschema.validate(peer_data, bma.ws.WS_PEER_SCHEMA)
+                            self._logger.debug("Received a peer")
+                            self.refresh_peer_data(peer_data)
+                        except TypeError as exception:
+                            self._logger.debug(exception)
+                    # Close session
+                    await client.close()
+
                 except (aiohttp.WSServerHandshakeError, ValueError) as e:
                     self._logger.debug(
                         "Websocket peer {0} : {1}".format(type(e).__name__, str(e))
@@ -400,7 +402,7 @@ class NodeConnector(QObject):
         for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
             try:
                 heads_data = await self.safe_request(
-                    endpoint, bma.network.heads, proxy=self._user_parameters.proxy()
+                    endpoint, bma.network.ws2p_heads, proxy=self._user_parameters.proxy()
                 )
                 if not heads_data:
                     continue
@@ -421,3 +423,30 @@ class NodeConnector(QObject):
 
     def handle_failure(self, weight=1):
         self.failure.emit(weight)
+
+
+def get_bma_endpoint_from_server_address(address: str, port: int, secured: bool) -> Union[BMAEndpoint, SecuredBMAEndpoint]:
+    """
+    Return a BMA Endpoint from server address parameters
+
+    :param address: Domain Name or IPV4 ou IPV6
+    :param port: Port number
+    :param secured: True if SSL secured
+    :return:
+    """
+    server = ""
+    ipv4 = ""
+    ipv6 = ""
+    if re.compile(HOST_REGEX).match(address):
+        server = address
+    elif re.compile(IPV4_REGEX).match(address):
+        ipv4 = address
+    elif re.compile(IPV6_REGEX).match(address):
+        ipv6 = address
+
+    if secured:
+        endpoint = SecuredBMAEndpoint(server, ipv4, ipv6, port, "")
+    else:
+        endpoint = BMAEndpoint(server, ipv4, ipv6, port)
+
+    return endpoint
diff --git a/src/sakia/data/entities/connection.py b/src/sakia/data/entities/connection.py
index 1f87c0ef..13be8ff8 100644
--- a/src/sakia/data/entities/connection.py
+++ b/src/sakia/data/entities/connection.py
@@ -1,6 +1,6 @@
 import attr
 from duniterpy.documents import block_uid, BlockUID
-from duniterpy.key import ScryptParams
+from duniterpy.key.scrypt_params import ScryptParams, SCRYPT_PARAMS
 
 
 @attr.s(hash=True)
@@ -14,9 +14,9 @@ class Connection:
     currency = attr.ib(converter=str)
     pubkey = attr.ib(converter=str)
     uid = attr.ib(converter=str, default="", cmp=False, hash=False)
-    scrypt_N = attr.ib(converter=int, default=4096, cmp=False, hash=False)
-    scrypt_r = attr.ib(converter=int, default=16, cmp=False, hash=False)
-    scrypt_p = attr.ib(converter=int, default=1, cmp=False, hash=False)
+    scrypt_N = attr.ib(converter=int, default=SCRYPT_PARAMS['N'], cmp=False, hash=False)
+    scrypt_r = attr.ib(converter=int, default=SCRYPT_PARAMS['r'], cmp=False, hash=False)
+    scrypt_p = attr.ib(converter=int, default=SCRYPT_PARAMS['p'], cmp=False, hash=False)
     blockstamp = attr.ib(
         converter=block_uid, default=BlockUID.empty(), cmp=False, hash=False
     )
diff --git a/src/sakia/data/entities/node.py b/src/sakia/data/entities/node.py
index aae5c01e..a3bbe474 100644
--- a/src/sakia/data/entities/node.py
+++ b/src/sakia/data/entities/node.py
@@ -1,14 +1,12 @@
 import attr
-from duniterpy.documents import block_uid, endpoint
+from duniterpy.documents import block_uid
+from duniterpy.api.endpoint import endpoint
 from sakia.helpers import attrs_tuple_of_str
 
 
 def _tuple_of_endpoints(value):
-    if isinstance(value, tuple):
+    if isinstance(value, tuple) or isinstance(value, list):
         return value
-    elif isinstance(value, list):
-        l = [endpoint(e) for e in value]
-        return tuple(l)
     elif isinstance(value, str):
         if value:
             list_of_str = value.split("\n")
diff --git a/src/sakia/data/processors/blockchain.py b/src/sakia/data/processors/blockchain.py
index 42ec51d5..a1d55408 100644
--- a/src/sakia/data/processors/blockchain.py
+++ b/src/sakia/data/processors/blockchain.py
@@ -2,11 +2,11 @@ import attr
 import sqlite3
 import logging
 from sakia.errors import NoPeerAvailable
-from ..entities import Blockchain, BlockchainParameters
+from ..entities import Blockchain
 from .nodes import NodesProcessor
 from ..connectors import BmaConnector
 from duniterpy.api import bma, errors
-from duniterpy.documents import Block, BMAEndpoint
+from duniterpy.documents import Block
 import asyncio
 
 
diff --git a/src/sakia/data/processors/nodes.py b/src/sakia/data/processors/nodes.py
index 50a6dd2f..b9ccb5b9 100644
--- a/src/sakia/data/processors/nodes.py
+++ b/src/sakia/data/processors/nodes.py
@@ -2,7 +2,7 @@ import attr
 import sqlite3
 from sakia.constants import ROOT_SERVERS
 from ..entities import Node
-from duniterpy.documents import BlockUID, endpoint
+from duniterpy.documents import BlockUID
 import logging
 import time
 
diff --git a/src/sakia/gui/dialogs/connection_cfg/view.py b/src/sakia/gui/dialogs/connection_cfg/view.py
index a84efe17..182786aa 100644
--- a/src/sakia/gui/dialogs/connection_cfg/view.py
+++ b/src/sakia/gui/dialogs/connection_cfg/view.py
@@ -3,7 +3,8 @@ from PyQt5.QtWidgets import QDialog
 from PyQt5.QtCore import pyqtSignal, Qt, QElapsedTimer, QDateTime, QCoreApplication
 from .connection_cfg_uic import Ui_ConnectionConfigurationDialog
 from .congratulation_uic import Ui_CongratulationPopup
-from duniterpy.key import SigningKey, ScryptParams
+from duniterpy.key import SigningKey
+from duniterpy.key.scrypt_params import ScryptParams
 from math import ceil, log
 from sakia.gui.widgets import toast
 from sakia.decorators import asyncify
diff --git a/src/sakia/gui/dialogs/contact/view.py b/src/sakia/gui/dialogs/contact/view.py
index 169fb09e..e69f81da 100644
--- a/src/sakia/gui/dialogs/contact/view.py
+++ b/src/sakia/gui/dialogs/contact/view.py
@@ -7,7 +7,7 @@ from PyQt5.QtWidgets import (
 )
 from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QModelIndex
 from .contact_uic import Ui_ContactDialog
-from duniterpy.documents.constants import pubkey_regex
+from duniterpy.constants import PUBKEY_REGEX
 import re
 
 
@@ -54,7 +54,7 @@ class ContactView(QDialog, Ui_ContactDialog):
 
     def check_pubkey(self):
         text = self.edit_pubkey.text()
-        re_pubkey = re.compile(pubkey_regex)
+        re_pubkey = re.compile(PUBKEY_REGEX)
         result = re_pubkey.match(text)
         if result:
             self.edit_pubkey.setStyleSheet("")
diff --git a/src/sakia/gui/dialogs/revocation/model.py b/src/sakia/gui/dialogs/revocation/model.py
index 49c7bfae..52630f88 100644
--- a/src/sakia/gui/dialogs/revocation/model.py
+++ b/src/sakia/gui/dialogs/revocation/model.py
@@ -1,4 +1,5 @@
-from duniterpy.documents import Revocation, BMAEndpoint, SecuredBMAEndpoint
+from duniterpy.documents import Revocation
+from duniterpy.api.endpoint import BMAEndpoint, SecuredBMAEndpoint
 from duniterpy.api import bma, errors
 from sakia.data.connectors import NodeConnector
 from asyncio import TimeoutError
diff --git a/src/sakia/gui/navigation/network/controller.py b/src/sakia/gui/navigation/network/controller.py
index 85568d5d..c0083286 100644
--- a/src/sakia/gui/navigation/network/controller.py
+++ b/src/sakia/gui/navigation/network/controller.py
@@ -4,7 +4,7 @@ from PyQt5.QtWidgets import QAction, QMenu
 from PyQt5.QtGui import QCursor, QDesktopServices
 from PyQt5.QtCore import pyqtSlot, QUrl, QObject
 from duniterpy.api import bma
-from duniterpy.documents import BMAEndpoint
+from duniterpy.api.endpoint import BMAEndpoint
 
 
 class NetworkController(QObject):
diff --git a/src/sakia/gui/navigation/network/table_model.py b/src/sakia/gui/navigation/network/table_model.py
index bd101e30..99447231 100644
--- a/src/sakia/gui/navigation/network/table_model.py
+++ b/src/sakia/gui/navigation/network/table_model.py
@@ -11,7 +11,7 @@ from PyQt5.QtCore import (
 )
 from PyQt5.QtGui import QColor, QFont, QIcon
 from sakia.data.entities import Node
-from duniterpy.documents import (
+from duniterpy.api.endpoint import (
     BMAEndpoint,
     SecuredBMAEndpoint,
     WS2PEndpoint,
diff --git a/src/sakia/gui/sub/transfer/controller.py b/src/sakia/gui/sub/transfer/controller.py
index 461964ee..1d1a1167 100644
--- a/src/sakia/gui/sub/transfer/controller.py
+++ b/src/sakia/gui/sub/transfer/controller.py
@@ -4,7 +4,7 @@ import logging
 from PyQt5.QtCore import Qt, QObject, pyqtSignal
 from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
 
-from duniterpy.documents.constants import pubkey_regex
+from duniterpy.constants import PUBKEY_REGEX
 from duniterpy.documents import CRCPubkey
 from sakia.data.processors import ConnectionsProcessor
 from sakia.decorators import asyncify
@@ -199,7 +199,7 @@ class TransferController(QObject):
                 if crc_pubkey.is_valid():
                     pubkey = crc_pubkey.pubkey
             except AttributeError:
-                result = re.compile("^({0})$".format(pubkey_regex)).match(
+                result = re.compile("^({0})$".format(PUBKEY_REGEX)).match(
                     self.view.pubkey_value()
                 )
                 if result:
diff --git a/src/sakia/gui/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py
index 5f639945..ae3baed9 100644
--- a/src/sakia/gui/widgets/context_menu.py
+++ b/src/sakia/gui/widgets/context_menu.py
@@ -3,7 +3,7 @@ import re
 from PyQt5.QtCore import QObject, pyqtSignal
 from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox
 
-from duniterpy.documents.constants import pubkey_regex
+from duniterpy.constants import PUBKEY_REGEX
 from duniterpy.documents.crc_pubkey import CRCPubkey
 from sakia.data.entities import Identity, Transaction, Dividend
 from sakia.data.processors import BlockchainProcessor, TransactionsProcessor
@@ -130,7 +130,7 @@ class ContextMenu(QObject):
 
     @staticmethod
     def _add_string_actions(menu, str_value):
-        if re.match(pubkey_regex, str_value):
+        if re.match(PUBKEY_REGEX, str_value):
             menu.qmenu.addSeparator().setText(str_value[:7])
             copy_pubkey = QAction(
                 menu.qmenu.tr("Copy pubkey to clipboard"), menu.qmenu.parent()
diff --git a/src/sakia/services/network.py b/src/sakia/services/network.py
index db811789..174754d0 100644
--- a/src/sakia/services/network.py
+++ b/src/sakia/services/network.py
@@ -5,9 +5,8 @@ import random
 
 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt
 from duniterpy.api import errors
-from duniterpy.documents import MalformedDocumentError
 from duniterpy.documents.ws2p.heads import *
-from duniterpy.documents.peer import BMAEndpoint
+from duniterpy.api.endpoint import BMAEndpoint
 from duniterpy.key import VerifyingKey
 from sakia.data.connectors import NodeConnector
 from sakia.data.entities import Node
-- 
GitLab