Skip to content
Snippets Groups Projects
graph.py 17.9 KiB
Newer Older
import logging
import time
import asyncio
from PyQt5.QtCore import QLocale, QDateTime
from ..core.registry import Identity, BlockchainState
from ..tools.decorators import asyncify
inso's avatar
inso committed
from ..tools.exceptions import NoPeerAvailable
from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_OUT, ARC_STATUS_STRONG, ARC_STATUS_WEAK


class Graph(object):
    def __init__(self, app, community, graph=None):
        """
        Init Graph instance
        :param cutecoin.core.app.Application app: Application instance
        :param cutecoin.core.community.Community community: Community instance
        :param dict graph: Dict of the graph
        self.community = community
        self.signature_validity = 0
        self.ARC_STATUS_STRONG_time = 0
        # graph empty if None parameter
        self._graph = graph or (dict() and (graph is None))

    @asyncio.coroutine
    def refresh_signature_validity(self):
        parameters = yield from self.community.parameters()
        self.signature_validity = parameters['sigValidity']
        #  arc considered strong during 75% of signature validity time
        self.ARC_STATUS_STRONG_time = int(self.signature_validity * 0.75)

    def set(self, graph):
        """
        Set the graph from dict
        :param dict graph:
        :return:
        """
        self._graph = graph

    def get(self):
        """
        Return the graph dict
        :return:
        """
        return self._graph
    @asyncio.coroutine
    def get_shortest_path_between_members(self, from_identity, to_identity):
        Return path list of nodes from from_identity to to_identity
        :param identity from_identity:
        :param identity to_identity:
        # if from_identity has no certifications, we can not make a path
        certifier_list = yield from from_identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
        certified_list = yield from from_identity.unique_valid_certified_by(self.app.identities_registry, self.community)
        print (certifier_list, certified_list)
        if not certifier_list and not certified_list:
            logging.debug('from_identity has no certifications : can not calculate wot path')
            return path

        # if to_identity has no certifications, we can not make a path
        certifier_list = yield from to_identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
        certified_list = yield from to_identity.unique_valid_certified_by(self.app.identities_registry, self.community)
        if not certifier_list and not certified_list:
            logging.debug('to_identity has no certifications : can not calculate wot path')
            return path

        logging.debug("path between %s to %s..." % (from_identity.uid, to_identity.uid))
        if from_identity.pubkey not in self._graph.keys():
            self.add_identity(from_identity)
            certifier_list = yield from from_identity.unique_valid_certifiers_of(self.app.identities_registry,
                                                                    self.community)
inso's avatar
inso committed
            yield from self.add_certifier_list(certifier_list, from_identity, to_identity)
            certified_list = yield from from_identity.unique_valid_certified_by(self.app.identities_registry,
                                                                   self.community)
inso's avatar
inso committed
            yield from self.add_certified_list(certified_list, from_identity, to_identity)
        if to_identity.pubkey not in self._graph.keys():
            # recursively feed graph searching for account node...
inso's avatar
inso committed
            yield from self.explore_to_find_member(to_identity,
                                                   self._graph[from_identity.pubkey]['connected'], list())
        if len(self._graph[from_identity.pubkey]['connected']) > 0 and to_identity.pubkey in self._graph:
            # calculate path of nodes between identity and to_identity
inso's avatar
inso committed
            path = yield from self.find_shortest_path(self._graph[from_identity.pubkey],
                                                      self._graph[to_identity.pubkey])

        if path:
            logging.debug([node['text'] for node in path])
        else:
            logging.debug('no wot path')

        return path

    @asyncio.coroutine
    def explore_to_find_member(self, identity, connected=None, done=None):
        Scan graph recursively to find identity
        :param identity identity:   identity instance to find
        :param list connected:  Optional, default=None, Pubkey list of the connected nodes
        around the current scanned node
        :param list done:       Optional, default=None, List of node already scanned
        :return: False when the identity is added in the graph
        # functions keywords args are persistent... Need to reset it with None trick
        connected = connected or (list() and (connected is None))
        done = done or (list() and (done is None))
        logging.debug("search %s in " % identity.uid)
        logging.debug([self._graph[pubkey]['text'] for pubkey in connected])
        # for each pubkey connected...
        for pubkey in tuple(connected):
            # capture node connected
            node = self._graph[pubkey]
            if node['id'] in tuple(done):
                continue
            identity_selected = identity.from_handled_data(node['text'], node['id'], BlockchainState.VALIDATED)
            certifier_list = yield from identity_selected.unique_valid_certifiers_of(self.app.identities_registry,
                                                                                     self.community)
inso's avatar
inso committed
            yield from self.add_certifier_list(certifier_list, identity_selected, identity)
            if identity.pubkey in tuple(self._graph.keys()):
            certified_list = yield from identity_selected.unique_valid_certified_by(self.app.identities_registry,
                                                                                    self.community)
inso's avatar
inso committed
            yield from self.add_certified_list(certified_list, identity_selected, identity)
            if identity.pubkey in tuple(self._graph.keys()):
            if node['id'] not in tuple(done):
                done.append(node['id'])
            if len(done) >= len(self._graph):
                return False
            found = yield from self.explore_to_find_member(identity,
inso's avatar
inso committed
                                                            self._graph[identity_selected.pubkey]['connected'],
                                                            done)
            if found:
                return True
    @asyncio.coroutine
    def find_shortest_path(self, start, end, path=None):
        """
        Find recursively the shortest path between two nodes
        :param dict start:  Start node
        :param dict end:    End node
        :param list path:   Optional, default=None, List of nodes
        path = path or (list() and (path is None))
        path = path + [start]
        if start['id'] == end['id']:
            return path
        if start['id'] not in self._graph.keys():
            return None
        shortest = None
        for pubkey in tuple(self._graph[start['id']]['connected']):
            node = self._graph[pubkey]
            if node not in path:
                newpath = yield from self.find_shortest_path(node, end, path)
                if newpath:
                    if not shortest or len(newpath) < len(shortest):
                        shortest = newpath
        return shortest

    @asyncio.coroutine
    def add_certifier_list(self, certifier_list, identity, identity_account):
        """
        Add list of certifiers to graph
        :param list certifier_list: List of certifiers from api
        :param identity identity:   identity instance which is certified
        :param identity identity_account:   Account identity instance
        if self.community:
inso's avatar
inso committed
            try:
                yield from self.refresh_signature_validity()
                #  add certifiers of uid
                for certifier in tuple(certifier_list):
                    # add only valid certification...
                    if (time.time() - certifier['cert_time']) > self.signature_validity:
inso's avatar
inso committed
                    # new node
                    if certifier['identity'].pubkey not in self._graph.keys():
                        node_status = 0
                        is_member = yield from certifier['identity'].is_member(self.community)
                        if certifier['identity'].pubkey == identity_account.pubkey:
                            node_status += NODE_STATUS_HIGHLIGHTED
                        if is_member is False:
                            node_status += NODE_STATUS_OUT
                        self._graph[certifier['identity'].pubkey] = {
                            'id': certifier['identity'].pubkey,
                            'arcs': list(),
                            'text': certifier['identity'].uid,
                            'tooltip': certifier['identity'].pubkey,
                            'status': node_status,
                            'connected': [identity.pubkey]
                        }
inso's avatar
inso committed
                    # keep only the latest certification
                    if self._graph[certifier['identity'].pubkey]['arcs']:
                        if certifier['cert_time'] < self._graph[certifier['identity'].pubkey]['arcs'][0]['cert_time']:
                            continue
                    # display validity status
                    if (time.time() - certifier['cert_time']) > self.ARC_STATUS_STRONG_time:
                        arc_status = ARC_STATUS_WEAK
                    else:
                        arc_status = ARC_STATUS_STRONG
inso's avatar
inso committed
                    arc = {
                        'id': identity.pubkey,
                        'status': arc_status,
                        'tooltip': QLocale.toString(
                            QLocale(),
                            QDateTime.fromTime_t(certifier['cert_time'] + self.signature_validity).date(),
                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
                        ),
                        'cert_time': certifier['cert_time']
                    }
                    current_block_number = self.community.network.current_blockid.number
                    if current_block_number and certifier['block_number']:
                        current_validations = current_block_number - certifier['block_number'] + 1
inso's avatar
inso committed
                        current_validations = 0
                    members_pubkeys = yield from self.community.members_pubkeys()
                    max_validation = self.community.network.fork_window(members_pubkeys) + 1
                    # Current validation can be negative if self.community.network.current_blockid.number
inso's avatar
inso committed
                    # is not refreshed yet
inso's avatar
inso committed
                    if max_validation > current_validations >= 0:
inso's avatar
inso committed
                        if self.app.preferences['expert_mode']:
                            arc['validation_text'] = "{0}/{1}".format(current_validations,
                                                                      max_validation)
                        else:
                            validation = current_validations / max_validation * 100
                            arc['validation_text'] = "{0} %".format(QLocale().toString(float(validation), 'f', 0))
                    else:
                        arc['validation_text'] = None

                    #  add arc to certifier
                    self._graph[certifier['identity'].pubkey]['arcs'].append(arc)
                    # if certifier node not in identity nodes
                    if certifier['identity'].pubkey not in tuple(self._graph[identity.pubkey]['connected']):
                        # add certifier node to identity node
                        self._graph[identity.pubkey]['connected'].append(certifier['identity'].pubkey)
            except NoPeerAvailable as e:
                logging.debug(str(e))
    @asyncio.coroutine
    def add_certified_list(self, certified_list, identity, identity_account):
        """
        Add list of certified from api to graph
        :param list certified_list: List of certified from api
        :param identity identity:   identity instance which is certifier
        :param identity identity_account:   Account identity instance
inso's avatar
inso committed
        if self.community:
            try:
                yield from self.refresh_signature_validity()
                # add certified by uid
                for certified in tuple(certified_list):
                    # add only valid certification...
                    if (time.time() - certified['cert_time']) > self.signature_validity:
                        continue
                    if certified['identity'].pubkey not in self._graph.keys():
                        node_status = 0
                        is_member = yield from certified['identity'].is_member(self.community)
                        if certified['identity'].pubkey == identity_account.pubkey:
                            node_status += NODE_STATUS_HIGHLIGHTED
                        if is_member is False:
                            node_status += NODE_STATUS_OUT
                        self._graph[certified['identity'].pubkey] = {
                            'id': certified['identity'].pubkey,
                            'arcs': list(),
                            'text': certified['identity'].uid,
                            'tooltip': certified['identity'].pubkey,
                            'status': node_status,
                            'connected': [identity.pubkey]
                        }
                    # display validity status
                    if (time.time() - certified['cert_time']) > self.ARC_STATUS_STRONG_time:
                        arc_status = ARC_STATUS_WEAK
                    else:
                        arc_status = ARC_STATUS_STRONG
                    arc = {
                        'id': certified['identity'].pubkey,
                        'status': arc_status,
                        'tooltip': QLocale.toString(
                            QLocale(),
                            QDateTime.fromTime_t(certified['cert_time'] + self.signature_validity).date(),
                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
                        ),
                        'cert_time': certified['cert_time']
                    }
                    current_block_number = self.community.network.current_blockid.number
                    if current_block_number and certified['block_number']:
                        current_validations = current_block_number - certified['block_number'] + 1
inso's avatar
inso committed
                    else:
                        current_validations = 0
                    members_pubkeys = yield from self.community.members_pubkeys()
                    max_validations = self.community.network.fork_window(members_pubkeys) + 1
                    if max_validations > current_validations >= 0:
inso's avatar
inso committed
                        if self.app.preferences['expert_mode']:
                            arc['validation_text'] = "{0}/{1}".format(current_validations,
                                                                      max_validations)
                        else:
                            validation = current_validations / max_validations * 100
                            validation = 100 if validation > 100 else validation
                            arc['validation_text'] = "{0} %".format(QLocale().toString(float(validation), 'f', 0))
                    else:
                        arc['validation_text'] = None
inso's avatar
inso committed
                    # replace old arc if this one is more recent
                    new_arc = True
                    index = 0
                    for a in self._graph[identity.pubkey]['arcs']:
                        # if same arc already exists...
                        if a['id'] == arc['id']:
                            # if arc more recent, dont keep old one...
                            if arc['cert_time'] >= a['cert_time']:
                                self._graph[identity.pubkey]['arcs'][index] = arc
                            new_arc = False
                        index += 1
inso's avatar
inso committed
                    #  if arc not in graph...
                    if new_arc:
                        # add arc in graph
                        self._graph[identity.pubkey]['arcs'].append(arc)
                    # if certified node not in identity nodes
                    if certified['identity'].pubkey not in tuple(self._graph[identity.pubkey]['connected']):
                        # add certified node to identity node
                        self._graph[identity.pubkey]['connected'].append(certified['identity'].pubkey)
            except NoPeerAvailable as e:
                logging.debug(str(e))
    def add_identity(self, identity, status=None, arcs=None, connected=None):
        Add identity as a new node in graph
        :param identity identity: identity instance
        :param int status:  Optional, default=None, Node status (see cutecoin.gui.views.wot)
        :param list arcs:  Optional, default=None, List of arcs (certified by identity)
        :param list connected:  Optional, default=None, Public key list of the connected nodes around the identity
        # functions keywords args are persistent... Need to reset it with None trick
        status = status or (0 and (status is None))
        arcs = arcs or (list() and (arcs is None))
        connected = connected or (list() and (connected is None))
        self._graph[identity.pubkey] = {
            'id': identity.pubkey,
            'arcs': arcs,
            'text': identity.uid,
            'tooltip': identity.pubkey,
            'status': status,