Skip to content
Snippets Groups Projects
account.py 9.16 KiB
'''
Created on 1 févr. 2014

@author: inso
'''

from ucoinpy import PROTOCOL_VERSION
from ucoinpy.api import bma
from ucoinpy.api.bma import ConnectionHandler
from ucoinpy.documents.peer import Peer
from ucoinpy.documents.certification import SelfCertification, Certification
from ucoinpy.documents.membership import Membership
from ucoinpy.key import SigningKey

import logging
import time

from PyQt5.QtCore import QObject, pyqtSignal, Qt

from .wallet import Wallet
from .community import Community
from .person import Person
from ..tools.exceptions import NoPeerAvailable, ContactAlreadyExists


def quantitative(units, community):
    return int(units)


def relative(units, community):
    ud = community.dividend
    relative_value = units / float(ud)
    return relative_value


def quantitative_zerosum(units, community):
    median = community.monetary_mass / community.nb_members
    return units - median


def relative_zerosum(units, community):
    median = community.monetary_mass / community.nb_members
    ud = community.dividend
    relative_value = units / float(ud)
    relative_median = median / ud
    return relative_value - relative_median


class Account(QObject):

    '''
    An account is specific to a key.
    Each account has only one key, and a key can
    be locally referenced by only one account.
    '''
    referentials = {'Units': (quantitative, '{0}', quantitative, '{0}'),
                    'UD': (relative, 'ud {0}', relative, 'ud {0}'),
                    'Quant Z-sum': (quantitative_zerosum, 'q0 {0}',
                                    quantitative, '{0}'),
                    'Relat Z-sum': (relative_zerosum, 'r0 {0}',
                                    relative, 'ud {0}')
                    }

    loading_progressed = pyqtSignal(int, int)

    def __init__(self, salt, pubkey, name, communities, wallets, contacts,
                 dead_communities):
        '''
        Constructor
        '''
        super().__init__()
        self.salt = salt
        self.pubkey = pubkey
        self.name = name
        self.communities = communities
        self.dead_communities = dead_communities
        self.wallets = wallets
        self.contacts = contacts
        self.referential = 'Units'

    @classmethod
    def create(cls, name, communities, wallets, confpath):
        '''
        Constructor
        '''
        account = cls(None, None, name, communities, wallets, [], [])
        return account

    @classmethod
    def load(cls, json_data):
        salt = json_data['salt']
        pubkey = json_data['pubkey']

        name = json_data['name']
        contacts = []

        for contact_data in json_data['contacts']:
            contacts.append(Person.from_json(contact_data))

        wallets = []
        for data in json_data['wallets']:
            wallets.append(Wallet.load(data))

        communities = []
        dead_communities = []
        for data in json_data['communities']:
            community = Community.load(data)
            communities.append(community)

        account = cls(salt, pubkey, name, communities, wallets,
                      contacts, dead_communities)
        return account

    def __eq__(self, other):
        if other is not None:
            return other.pubkey == self.pubkey
        else:
            return False

    def check_password(self, password):
        key = SigningKey(self.salt, password)
        return (key.pubkey == self.pubkey)

    def add_contact(self, person):
        same_contact = [contact for contact in self.contacts
                        if person.pubkey == contact.pubkey]

        if len(same_contact) > 0:
            raise ContactAlreadyExists(person.uid, same_contact[0].uid)
        self.contacts.append(person)

    def add_community(self, community):
        logging.debug("Adding a community")
        self.communities.append(community)
        return community

    def refresh_cache(self):
        loaded_wallets = 0

        def progressing(value, maximum):
            account_value = maximum * len(self.communities) * loaded_wallets + value
            account_max = maximum * len(self.communities) * len(self.wallets)
            self.loading_progressed.emit(account_value, account_max)

        for w in self.wallets:
            w.refresh_progressed.connect(progressing, type=Qt.DirectConnection)
            for c in self.communities:
                w.refresh_cache(c)
                loaded_wallets = loaded_wallets + 1

    def set_display_referential(self, index):
        self.referential = index

    @property
    def units_to_ref(self):
        return Account.referentials[self.referential][0]

    @property
    def units_to_diff_ref(self):
        return Account.referentials[self.referential][2]

    def ref_name(self, currency):
        return Account.referentials[self.referential][1].format(currency)

    def diff_ref_name(self, currency):
        return Account.referentials[self.referential][3].format(currency)

    def set_walletpool_size(self, size, password):
        logging.debug("Defining wallet pool size")
        if len(self.wallets) < size:
            for i in range(len(self.wallets), size):
                wallet = Wallet.create(i, self.salt, password, "Wallet {0}".format(i))
                self.wallets.append(wallet)
        else:
            self.wallets = self.wallets[:size]

    def certify(self, password, community, pubkey):
        certified = Person.lookup(pubkey, community)
        blockid = community.current_blockid()

        certification = Certification(PROTOCOL_VERSION, community.currency,
                                      self.pubkey, certified.pubkey,
                                      blockid['number'], blockid['hash'], None)

        selfcert = certified.selfcert(community)
        logging.debug("SelfCertification : {0}".format(selfcert.raw()))

        key = SigningKey(self.salt, password)
        certification.sign(selfcert, [key])
        signed_cert = certification.signed_raw(selfcert)
        logging.debug("Certification : {0}".format(signed_cert))

        data = {'pubkey': certified.pubkey,
                        'self_': selfcert.signed_raw(),
                        'other': "{0}\n".format(certification.inline())}
        logging.debug("Posted data : {0}".format(data))
        community.broadcast(bma.wot.Add, {}, data)

    def sources(self, community):
        sources = []
        for w in self.wallets:
            for s in w.sources(community):
                sources.append(s)
        return sources

    def transfers(self, community):
        sent = []
        for w in self.wallets:
            for transfer in w.transfers(community):
                sent.append(transfer)
        return sent

    def amount(self, community):
        value = 0
        for w in self.wallets:
            value += w.value(community)
        return value

    def member_of(self, community):
        pubkeys = community.members_pubkeys()
        if self.pubkey not in pubkeys:
            logging.debug("{0} not found in members : {1}".format(self.pubkey,
                                                                  pubkeys))
            return False
        return True

    def send_pubkey(self, password, community):
        selfcert = SelfCertification(PROTOCOL_VERSION,
                                     community.currency,
                                     self.pubkey,
                                     int(time.time()),
                                     self.name,
                                     None)
        key = SigningKey(self.salt, password)
        selfcert.sign([key])
        logging.debug("Key publish : {0}".format(selfcert.signed_raw()))
        community.broadcast(bma.wot.Add, {}, {'pubkey': self.pubkey,
                                    'self_': selfcert.signed_raw(),
                                    'other': []})

    def send_membership(self, password, community, type):
        self_ = Person.lookup(self.pubkey, community)
        selfcert = self_.selfcert(community)

        blockid = community.current_blockid()

        membership = Membership(PROTOCOL_VERSION, community.currency,
                          selfcert.pubkey, blockid['number'],
                          blockid['hash'], type, selfcert.uid,
                          selfcert.timestamp, None)
        key = SigningKey(self.salt, password)
        membership.sign([key])
        logging.debug("Membership : {0}".format(membership.signed_raw()))
        community.broadcast(bma.blockchain.Membership, {},
                       {'membership': membership.signed_raw()})

    def jsonify(self):
        data_communities = []
        communities = self.communities + self.dead_communities
        for c in communities:
            data_communities.append(c.jsonify())

        data_wallets = []
        for w in self.wallets:
            data_wallets.append(w.jsonify())

        data_contacts = []
        for p in self.contacts:
            data_contacts.append(p.jsonify())

        data = {'name': self.name,
                'salt': self.salt,
                'pubkey': self.pubkey,
                'communities': data_communities,
                'wallets': data_wallets,
                'contacts': data_contacts}
        return data

    def get_person(self):
        return Person(self.name, self.pubkey)