Skip to content
Snippets Groups Projects
Commit 4a855be8 authored by inso's avatar inso
Browse files

Merge community methods into new data layer

parent a023bc09
No related branches found
No related tags found
No related merge requests found
"""
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
......@@ -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)
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
......
......@@ -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)
......
......@@ -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
......
......@@ -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):
"""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment