diff --git a/src/sakia/core/community.py b/src/sakia/core/community.py deleted file mode 100644 index cb640069c0b07166b999beb2347d9ea5cefc54d7..0000000000000000000000000000000000000000 --- a/src/sakia/core/community.py +++ /dev/null @@ -1,343 +0,0 @@ -""" -Created on 1 févr. 2014 - -@author: inso -""" - -import logging -import re -import math - -from PyQt5.QtCore import QObject - -from sakia.errors import NoPeerAvailable -from sakia.data.processors import NodesProcessor -from duniterpy.api import bma, errors -from sakia.data.connectors import BmaConnector - - -class Community(QObject): - """ - A community is a group of nodes using the same currency. - - .. warning:: The currency name is supposed to be unique in sakia - but nothing exists in duniter to assert that a currency name is unique. - """ - def __init__(self, currency, nodes_processor, bma_connector): - """ - Initialize community attributes with a currency and a network. - - :param str currency: The currency name of the community. - :param sakia.data.processors.NodesProcessor nodes_processor: The network of the community - :param sakia.data.connectors.BmaConnector bma_connector: The BMA connector object - - .. warning:: The community object should be created using its factory - class methods. - """ - super().__init__() - self.currency = currency - self._nodes_processor = nodes_processor - self._bma_connector = bma_connector - - @classmethod - def create(cls, node): - """ - Create a community from its first node. - - :param node: The first Node of the community - """ - network = Network.create(node) - bma_access = BmaAccess.create(network) - community = cls(node.currency, network, bma_access) - logging.debug("Creating community") - return community - - @classmethod - def load(cls, json_data, file_version): - """ - Load a community from json - - :param dict json_data: The community as a dict in json format - :param NormalizedVersion file_version: the file sakia version - """ - currency = json_data['currency'] - network = Network.from_json(currency, json_data['peers'], file_version) - bma_access = BmaAccess.create(network) - community = cls(currency, network, bma_access) - return community - - @property - def name(self): - """ - The community name is its currency name. - - :return: The community name - """ - return self.currency - - @property - def short_currency(self): - """ - Format the currency name to a short one - - :return: The currency name in a shot format. - """ - words = re.split('[_\W]+', self.currency) - shortened = "" - if len(words) > 1: - shortened = ''.join([w[0] for w in words]) - else: - vowels = ('a', 'e', 'i', 'o', 'u', 'y') - shortened = self.currency - shortened = ''.join([c for c in shortened if c not in vowels]) - return shortened.upper() - - @property - def currency_symbol(self): - """ - Format the currency name to a symbol one. - - :return: The currency name as a utf-8 circled symbol. - """ - letter = self.currency[0] - u = ord('\u24B6') + ord(letter) - ord('A') - return chr(u) - - async def dividend(self, block_number=None): - """ - Get the last generated community universal dividend before block_number. - If block_number is None, returns the last block_number. - - :param int block_number: The block at which we get the latest dividend - - :return: The last UD or 1 if no UD was generated. - """ - block = await self.get_ud_block(block_number=block_number) - if block: - return block['dividend'] * math.pow(10, block['unitbase']) - else: - return 1 - - async def computed_dividend(self): - """ - Get the computed community universal dividend. - - Calculation based on t = last UD block time and on values from the that block : - - UD(computed) = CEIL(MAX(UD(t) ; c * M(t) / N(t))) - - :return: The computed UD or 1 if no UD was generated. - """ - block = await self.get_ud_block() - if block: - parameters = await self.parameters() - return math.ceil( - max( - (await self.dividend()), - float(0) if block['membersCount'] == 0 else - parameters['c'] * block['monetaryMass'] / block['membersCount'] - ) - ) - - else: - return 1 - - async def get_ud_block(self, x=0, block_number=None): - """ - Get a block with universal dividend - If x and block_number are passed to the result, - it returns the 'x' older block with UD in it BEFORE block_number - - :param int x: Get the 'x' older block with UD in it - :param int block_number: Get the latest dividend before this block number - :return: The last block with universal dividend. - :rtype: dict - """ - try: - udblocks = await self.bma_access.future_request(bma.blockchain.UD) - blocks = udblocks['result']['blocks'] - if block_number: - blocks = [b for b in blocks if b <= block_number] - if len(blocks) > 0: - index = len(blocks) - (1+x) - if index < 0: - index = 0 - block_number = blocks[index] - block = await self.bma_access.future_request(bma.blockchain.Block, - req_args={'number': block_number}) - return block - else: - return None - except errors.DuniterError as e: - if e.ucode == errors.BLOCK_NOT_FOUND: - logging.debug(str(e)) - return None - except NoPeerAvailable as e: - logging.debug(str(e)) - return None - - async def monetary_mass(self): - """ - Get the community monetary mass - - :return: The monetary mass value - """ - # Get cached block by block number - block_number = self.network.current_blockUID.number - if block_number: - block = await self.bma_access.future_request(bma.blockchain.Block, - req_args={'number': block_number}) - return block['monetaryMass'] - else: - return 0 - - async def nb_members(self): - """ - Get the community members number - - :return: The community members number - """ - try: - # Get cached block by block number - block_number = self.network.current_blockUID.number - block = await self.bma_access.future_request(bma.blockchain.Block, - req_args={'number': block_number}) - return block['membersCount'] - except errors.DuniterError as e: - if e.ucode == errors.BLOCK_NOT_FOUND: - return 0 - except NoPeerAvailable as e: - logging.debug(str(e)) - return 0 - - async def time(self, block_number=None): - """ - Get the blockchain time - :param block_number: The block number, None if current block - :return: The community blockchain time - :rtype: int - """ - try: - # Get cached block by block number - if block_number is None: - block_number = self.network.current_blockUID.number - block = await self.bma_access.future_request(bma.blockchain.Block, - req_args={'number': block_number}) - return block['medianTime'] - except errors.DuniterError as e: - if e.ucode == errors.BLOCK_NOT_FOUND: - return 0 - except NoPeerAvailable as e: - logging.debug(str(e)) - return 0 - - @property - def network(self): - """ - Get the community network instance. - - :return: The community network instance. - :rtype: sakia.core.net.Network - """ - return self._network - - @property - def bma_access(self): - """ - Get the community bma_access instance - - :return: The community bma_access instace - :rtype: sakia.core.net.api.bma.access.BmaAccess - """ - return self._bma_access - - async def parameters(self): - """ - Return community parameters in bma format - """ - return await self.bma_access.future_request(bma.blockchain.Parameters) - - async def certification_expired(self, cert_time): - """ - Return True if the certificaton time is too old - - :param int cert_time: the timestamp of the certification - """ - parameters = await self.parameters() - blockchain_time = await self.time() - return blockchain_time - cert_time > parameters['sigValidity'] - - async def certification_writable(self, cert_time): - """ - Return True if the certificaton time is too old - - :param int cert_time: the timestamp of the certification - """ - parameters = await self.parameters() - blockchain_time = await self.time() - return blockchain_time - cert_time < parameters['sigWindow'] * parameters['avgGenTime'] - - def add_node(self, node): - """ - Add a peer to the community. - - :param peer: The new peer as a duniterpy Peer object. - """ - self._network.add_root_node(node) - - def remove_node(self, index): - """ - Remove a node from the community. - - :param index: The index of the removed node. - """ - self._network.remove_root_node(index) - - async def get_block(self, number=None): - """ - Get a block - - :param int number: The block number. If none, returns current block. - """ - if number is None: - block_number = self.network.current_blockUID.number - data = await self.bma_access.future_request(bma.blockchain.Block, - req_args={'number': block_number}) - else: - logging.debug("Requesting block {0}".format(number)) - data = await self.bma_access.future_request(bma.blockchain.Block, - req_args={'number': number}) - return data - - async def members_pubkeys(self): - """ - Listing members pubkeys of a community - - :return: All members pubkeys. - """ - memberships = await self.bma_access.future_request(bma.wot.Members) - return [m['pubkey'] for m in memberships["results"]] - - def start_coroutines(self): - self.network.start_coroutines() - - async def stop_coroutines(self, closing=False): - await self.network.stop_coroutines(closing) - - def rollback_cache(self): - self._bma_access.rollback() - - def jsonify(self): - """ - Jsonify the community datas. - - :return: The community as a dict in json format. - """ - - nodes_data = [] - for node in self._network.root_nodes: - nodes_data.append(node.jsonify_root_node()) - - data = {'currency': self.currency, - 'peers': nodes_data} - return data diff --git a/src/sakia/data/entities/blockchain.py b/src/sakia/data/entities/blockchain.py index 72d44d383a99239aee92d7ab50345c55d3d304fe..7e5b3ef9f627a7035ee53543da2a4743475c5c53 100644 --- a/src/sakia/data/entities/blockchain.py +++ b/src/sakia/data/entities/blockchain.py @@ -5,37 +5,37 @@ from duniterpy.documents import block_uid, BlockUID @attr.s() class BlockchainParameters: # The decimal percent growth of the UD every [dt] period - c = attr.ib(convert=float, default=0, cmp=False) + c = attr.ib(convert=float, default=0, cmp=False, hash=False) # Time period between two UD in seconds - dt = attr.ib(convert=int, default=0, cmp=False) + dt = attr.ib(convert=int, default=0, cmp=False, hash=False) # UD(0), i.e. initial Universal Dividend - ud0 = attr.ib(convert=int, default=0, cmp=False) + ud0 = attr.ib(convert=int, default=0, cmp=False, hash=False) # Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero - sig_period = attr.ib(convert=int, default=0, cmp=False) + sig_period = attr.ib(convert=int, default=0, cmp=False, hash=False) # Maximum quantity of active certifications made by member - sig_stock = attr.ib(convert=int, default=0, cmp=False) + sig_stock = attr.ib(convert=int, default=0, cmp=False, hash=False) # Maximum delay in seconds a certification can wait before being expired for non-writing - sig_window = attr.ib(convert=int, default=0, cmp=False) + sig_window = attr.ib(convert=int, default=0, cmp=False, hash=False) # Maximum age of a active signature (in seconds) - sig_validity = attr.ib(convert=int, default=0, cmp=False) + sig_validity = attr.ib(convert=int, default=0, cmp=False, hash=False) # Minimum quantity of signatures to be part of the WoT - sig_qty = attr.ib(convert=int, default=0, cmp=False) + sig_qty = attr.ib(convert=int, default=0, cmp=False, hash=False) # Minimum decimal percent of sentries to reach to match the distance rule - xpercent = attr.ib(convert=float, default=0, cmp=False) + xpercent = attr.ib(convert=float, default=0, cmp=False, hash=False) # Maximum age of an active membership( in seconds) - ms_validity = attr.ib(convert=int, default=0, cmp=False) + ms_validity = attr.ib(convert=int, default=0, cmp=False, hash=False) # Maximum distance between each WoT member and a newcomer - step_max = attr.ib(convert=int, default=0, cmp=False) + step_max = attr.ib(convert=int, default=0, cmp=False, hash=False) # Number of blocks used for calculating median time - median_time_blocks = attr.ib(convert=int, default=0, cmp=False) + median_time_blocks = attr.ib(convert=int, default=0, cmp=False, hash=False) # The average time for writing 1 block (wished time) in seconds - avg_gen_time = attr.ib(convert=int, default=0, cmp=False) + avg_gen_time = attr.ib(convert=int, default=0, cmp=False, hash=False) # The number of blocks required to evaluate again PoWMin value - dt_diff_eval = attr.ib(convert=int, default=0, cmp=False) + dt_diff_eval = attr.ib(convert=int, default=0, cmp=False, hash=False) # The number of previous blocks to check for personalized difficulty - blocks_rot = attr.ib(convert=int, default=0, cmp=False) + blocks_rot = attr.ib(convert=int, default=0, cmp=False, hash=False) # The decimal percent of previous issuers to reach for personalized difficulty - percent_rot = attr.ib(convert=float, default=0, cmp=False) + percent_rot = attr.ib(convert=float, default=0, cmp=False, hash=False) @attr.s() @@ -45,16 +45,18 @@ class Blockchain: # block number and hash current_buid = attr.ib(convert=block_uid, default=BlockUID.empty()) # Number of members - nb_members = attr.ib(convert=int, default=0, cmp=False) + nb_members = attr.ib(convert=int, default=0, cmp=False, hash=False) # Current monetary mass in units - current_mass = attr.ib(convert=int, default=0, cmp=False) + current_mass = attr.ib(convert=int, default=0, cmp=False, hash=False) # Median time in seconds - median_time = attr.ib(convert=int, default=0, cmp=False) + median_time = attr.ib(convert=int, default=0, cmp=False, hash=False) # Last UD amount in units (multiply by 10^base) - last_ud = attr.ib(convert=int, default=0, cmp=False) + last_ud = attr.ib(convert=int, default=0, cmp=False, hash=False) # Last UD base - last_ud_base = attr.ib(convert=int, default=0, cmp=False) + last_ud_base = attr.ib(convert=int, default=0, cmp=False, hash=False) + # Last UD base + last_ud_time = attr.ib(convert=int, default=0, cmp=False, hash=False) # Previous monetary mass in units - previous_mass = attr.ib(convert=int, default=0, cmp=False) + previous_mass = attr.ib(convert=int, default=0, cmp=False, hash=False) # Currency name - currency = attr.ib(convert=str, default="", cmp=False) + currency = attr.ib(convert=str, default="", cmp=False, hash=False) diff --git a/src/sakia/data/processors/blockchain.py b/src/sakia/data/processors/blockchain.py index 2b862745ba4918b451c03b38d599191b79476e5b..358263e14539bd541fc4a58b373d86e49553862a 100644 --- a/src/sakia/data/processors/blockchain.py +++ b/src/sakia/data/processors/blockchain.py @@ -1,4 +1,5 @@ import attr +import re from ..entities import Blockchain from duniterpy.api import bma from duniterpy.documents import Block @@ -18,6 +19,70 @@ class BlockchainProcessor: """ return self._repo.get_one({'currency': self._currency}).current_buid + def time(self): + """ + Get the local current median time + :rtype: int + """ + return self._repo.get_one({'currency': self._currency}).median_time + + def parameters(self): + """ + Get the parameters of the blockchain + :rtype: sakia.data.entities.BlockchainParameters + """ + return self._repo.get_one({'currency': self._currency}).parameters + + def monetary_mass(self): + """ + Get the local current monetary mass + :rtype: int + """ + return self._repo.get_one({'currency': self._currency}).monetary_mass + + def nb_members(self): + """ + Get the number of members in the blockchain + :rtype: int + """ + return self._repo.get_one({'currency': self._currency}).nb_members + + def last_ud(self): + """ + Get the last ud value and base + :rtype: int, int + """ + blockchain = self._repo.get_one({'currency': self._currency}) + return blockchain.last_ud, blockchain.last_ud_base + + @property + def short_currency(self): + """ + Format the currency name to a short one + + :return: The currency name in a shot format. + """ + words = re.split('[_\W]+', self.currency) + shortened = "" + if len(words) > 1: + shortened = ''.join([w[0] for w in words]) + else: + vowels = ('a', 'e', 'i', 'o', 'u', 'y') + shortened = self.currency + shortened = ''.join([c for c in shortened if c not in vowels]) + return shortened.upper() + + @property + def currency_symbol(self): + """ + Format the currency name to a symbol one. + + :return: The currency name as a utf-8 circled symbol. + """ + letter = self.currency[0] + u = ord('\u24B6') + ord(letter) - ord('A') + return chr(u) + async def new_blocks_with_identities(self): """ Get blocks more recent than local blockuid diff --git a/src/sakia/services/blockchain.py b/src/sakia/services/blockchain.py index f8936168ebadcfcd8ed872432f2cec233dfeac5b..7bbc2d4b704cc44da3b1dbd0d8e94db282d87da9 100644 --- a/src/sakia/services/blockchain.py +++ b/src/sakia/services/blockchain.py @@ -28,7 +28,6 @@ class BlockchainService(QObject): Handle a new current block uid :param duniterpy.documents.BlockUID new_block_uid: the new current blockuid """ - local_current_buid = self._blockchain_processor.current_buid() with_identities = await self._blockchain_processor.new_blocks_with_identities() with_money = await self._blockchain_processor.new_blocks_with_money() blocks = await self._blockchain_processor.blocks(with_identities + with_money) diff --git a/src/sakia/services/identities.py b/src/sakia/services/identities.py index 85691dd6138479265d554c610c3dcfcc38c685ce..13c878add583aafd53453457bd12bf767f0abb28 100644 --- a/src/sakia/services/identities.py +++ b/src/sakia/services/identities.py @@ -8,21 +8,43 @@ class IdentitiesService(QObject): Identities service is managing identities data received to update data locally """ - def __init__(self, currency, identities_processor, certs_processor, bma_connector): + def __init__(self, currency, identities_processor, certs_processor, blockchain_processor, bma_connector): """ Constructor the identities service :param str currency: The currency name of the community :param sakia.data.processors.IdentitiesProcessor identities_processor: the identities processor for given currency :param sakia.data.processors.CertificationsProcessor certs_processor: the certifications processor for given currency + :param sakia.data.processors.BlockchainProcessor certs_processor: the blockchain processor for given currency :param sakia.data.connectors.BmaConnector bma_connector: The connector to BMA API """ super().__init__() self._identities_processor = identities_processor self._certs_processor = certs_processor + self._blockchain_processor = blockchain_processor self._bma_connector = bma_connector self.currency = currency + def certification_expired(self, cert_time): + """ + Return True if the certificaton time is too old + + :param int cert_time: the timestamp of the certification + """ + parameters = self._blockchain_processor.parameters() + blockchain_time = self._blockchain_processor.median_time() + return blockchain_time - cert_time > parameters.sig_validity + + def certification_writable(self, cert_time): + """ + Return True if the certificaton time is too old + + :param int cert_time: the timestamp of the certification + """ + parameters = self._blockchain_processor.parameters() + blockchain_time = self._blockchain_processor.median_time() + return blockchain_time - cert_time < parameters.sig_window * parameters.avg_gen_time + def _parse_revocations(self, block): """ Parse revoked pubkeys found in a block and refresh local data diff --git a/src/sakia/services/network.py b/src/sakia/services/network.py index e4d5c779fecfba887a0fe2fca1c6e1606982b581..9f12bbe1ec5525c4c983ee2c9589f91fd603529b 100644 --- a/src/sakia/services/network.py +++ b/src/sakia/services/network.py @@ -17,8 +17,6 @@ class NetworkService(QObject): """ nodes_changed = pyqtSignal() root_nodes_changed = pyqtSignal() - blockchain_progress = pyqtSignal(int) - blockchain_rollback = pyqtSignal(int) def __init__(self, currency, processor, connectors, session): """