diff --git a/src/sakia/data/entities/__init__.py b/src/sakia/data/entities/__init__.py index 28edfe53662ce8a0f0d90de545a274fa37729afd..40b3174b2e6f611d6ac04904801669e942085e5a 100644 --- a/src/sakia/data/entities/__init__.py +++ b/src/sakia/data/entities/__init__.py @@ -1,5 +1,5 @@ from .identity import Identity -from .community import Community +from .blockchain import Blockchain, BlockchainParameters from .certification import Certification from .transaction import Transaction from .node import Node diff --git a/src/sakia/data/entities/blockchain.py b/src/sakia/data/entities/blockchain.py index 62e5b495947f0c6cb627a272cd2a2d32c2b94533..72d44d383a99239aee92d7ab50345c55d3d304fe 100644 --- a/src/sakia/data/entities/blockchain.py +++ b/src/sakia/data/entities/blockchain.py @@ -2,13 +2,59 @@ import attr 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) + # Time period between two UD in seconds + dt = attr.ib(convert=int, default=0, cmp=False) + # UD(0), i.e. initial Universal Dividend + ud0 = attr.ib(convert=int, default=0, cmp=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) + # Maximum quantity of active certifications made by member + sig_stock = attr.ib(convert=int, default=0, cmp=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) + # Maximum age of a active signature (in seconds) + sig_validity = attr.ib(convert=int, default=0, cmp=False) + # Minimum quantity of signatures to be part of the WoT + sig_qty = attr.ib(convert=int, default=0, cmp=False) + # Minimum decimal percent of sentries to reach to match the distance rule + xpercent = attr.ib(convert=float, default=0, cmp=False) + # Maximum age of an active membership( in seconds) + ms_validity = attr.ib(convert=int, default=0, cmp=False) + # Maximum distance between each WoT member and a newcomer + step_max = attr.ib(convert=int, default=0, cmp=False) + # Number of blocks used for calculating median time + median_time_blocks = attr.ib(convert=int, default=0, cmp=False) + # The average time for writing 1 block (wished time) in seconds + avg_gen_time = attr.ib(convert=int, default=0, cmp=False) + # The number of blocks required to evaluate again PoWMin value + dt_diff_eval = attr.ib(convert=int, default=0, cmp=False) + # The number of previous blocks to check for personalized difficulty + blocks_rot = attr.ib(convert=int, default=0, cmp=False) + # The decimal percent of previous issuers to reach for personalized difficulty + percent_rot = attr.ib(convert=float, default=0, cmp=False) + + @attr.s() class Blockchain: + # Parameters in block 0 + parameters = attr.ib(default=BlockchainParameters()) + # 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) + # Current monetary mass in units current_mass = attr.ib(convert=int, default=0, cmp=False) + # Median time in seconds median_time = attr.ib(convert=int, default=0, cmp=False) + # Last UD amount in units (multiply by 10^base) last_ud = attr.ib(convert=int, default=0, cmp=False) + # Last UD base last_ud_base = attr.ib(convert=int, default=0, cmp=False) + # Previous monetary mass in units previous_mass = attr.ib(convert=int, default=0, cmp=False) + # Currency name currency = attr.ib(convert=str, default="", cmp=False) diff --git a/src/sakia/data/entities/community.py b/src/sakia/data/entities/community.py deleted file mode 100644 index 2e483c091e0286046873def1bb908a573c12b56a..0000000000000000000000000000000000000000 --- a/src/sakia/data/entities/community.py +++ /dev/null @@ -1,42 +0,0 @@ -import attr - - -@attr.s() -class Community: - # The decimal percent growth of the UD every [dt] period - c = attr.ib(convert=float, default=0, cmp=False) - # Time period between two UD in seconds - dt = attr.ib(convert=int, default=0, cmp=False) - # UD(0), i.e. initial Universal Dividend - ud0 = attr.ib(convert=int, default=0, cmp=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) - # Maximum quantity of active certifications made by member - sig_stock = attr.ib(convert=int, default=0, cmp=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) - # Maximum age of a active signature (in seconds) - sig_validity = attr.ib(convert=int, default=0, cmp=False) - # Minimum quantity of signatures to be part of the WoT - sig_qty = attr.ib(convert=int, default=0, cmp=False) - # Minimum decimal percent of sentries to reach to match the distance rule - xpercent = attr.ib(convert=float, default=0, cmp=False) - # Maximum age of an active membership( in seconds) - ms_validity = attr.ib(convert=int, default=0, cmp=False) - # Maximum distance between each WoT member and a newcomer - step_max = attr.ib(convert=int, default=0, cmp=False) - # Number of blocks used for calculating median time - median_time_blocks = attr.ib(convert=int, default=0, cmp=False) - # The average time for writing 1 block (wished time) in seconds - avg_gen_time = attr.ib(convert=int, default=0, cmp=False) - # The number of blocks required to evaluate again PoWMin value - dt_diff_eval = attr.ib(convert=int, default=0, cmp=False) - # The number of previous blocks to check for personalized difficulty - blocks_rot = attr.ib(convert=int, default=0, cmp=False) - # The decimal percent of previous issuers to reach for personalized difficulty - percent_rot = attr.ib(convert=float, default=0, cmp=False) - # Currency name - currency = attr.ib(convert=str, default="", cmp=False) - - - diff --git a/src/sakia/data/processors/communities.py b/src/sakia/data/processors/communities.py deleted file mode 100644 index b9ad62a4e09e8bff3f4f7d4d2f9187dc139a656f..0000000000000000000000000000000000000000 --- a/src/sakia/data/processors/communities.py +++ /dev/null @@ -1,16 +0,0 @@ -import attr - - -@attr.s -class CommunityProcessor: - _repo = attr.ib() # :type sakia.data.repositories.CommunitiesRepo - - async def get_from_currency(self, currency): - """ - Get the community of a currency - - :param currency: - :rtype: sakia.data.entities.Community - """ - return self._repo.get_one(currency=currency) - diff --git a/src/sakia/data/repositories/__init__.py b/src/sakia/data/repositories/__init__.py index b34c4a698b7ae020250e3c384e2099058df6f9c8..a7e8f3667e2f6335493a5e1f0c291265f7aa2eb2 100644 --- a/src/sakia/data/repositories/__init__.py +++ b/src/sakia/data/repositories/__init__.py @@ -1,6 +1,6 @@ from .identities import IdentitiesRepo -from .communities import CommunitiesRepo +from .blockchains import BlockchainsRepo from .meta import MetaDatabase from .certifications import CertificationsRepo from .transactions import TransactionsRepo -from .nodes import NodesRepo \ No newline at end of file +from .nodes import NodesRepo diff --git a/src/sakia/data/repositories/blockchains.py b/src/sakia/data/repositories/blockchains.py new file mode 100644 index 0000000000000000000000000000000000000000..055deab24b566301fa92073e7b8fc9b0d1ac13f8 --- /dev/null +++ b/src/sakia/data/repositories/blockchains.py @@ -0,0 +1,117 @@ +from typing import List + +import attr + +from ..entities import Blockchain, BlockchainParameters + + +@attr.s(frozen=True) +class BlockchainsRepo: + """The repository for Blockchain entities. + """ + _conn = attr.ib() # :type sqlite3.Connection + _primary_keys = (Blockchain.currency,) + + def insert(self, blockchain): + """ + Commit a blockchain to the database + :param sakia.data.entities.Blockchain blockchain: the blockchain to commit + """ + with self._conn: + blockchain_tuple = attr.astuple(blockchain.parameters) \ + + attr.astuple(blockchain, filter=attr.filters.exclude(Blockchain.parameters)) + values = ",".join(['?'] * len(blockchain_tuple)) + self._conn.execute("INSERT INTO blockchains VALUES ({0})".format(values), blockchain_tuple) + + def update(self, blockchain): + """ + Update an existing blockchain in the database + :param sakia.data.entities.Blockchain blockchain: the blockchain to update + """ + with self._conn: + updated_fields = attr.astuple(blockchain, filter=attr.filters.exclude( + Blockchain.parameters, *BlockchainsRepo._primary_keys)) + where_fields = attr.astuple(blockchain, filter=attr.filters.include(*BlockchainsRepo._primary_keys)) + self._conn.execute("""UPDATE blockchains SET + current_buid=?, + nb_members=?, + current_mass=?, + median_time=?, + last_ud=?, + last_ud_base=?, + previous_mass=? + WHERE + currency=?""", + updated_fields + where_fields) + + def get_one(self, **search): + """ + Get an existing blockchain in the database + :param dict search: the criterions of the lookup + :rtype: sakia.data.entities.Blockchain + """ + with self._conn: + filters = [] + values = [] + for k, v in search.items(): + filters.append("{k}=?".format(k=k)) + values.append(v) + + request = "SELECT * FROM blockchains WHERE {filters}".format(filters=" AND ".join(filters)) + + c = self._conn.execute(request, tuple(values)) + data = c.fetchone() + if data: + return Blockchain(BlockchainParameters(*data[:15]), *data[16:]) + + def get_all(self, offset=0, limit=1000, sort_by="currency", sort_order="ASC", **search) -> List[Blockchain]: + """ + Get all existing blockchain in the database corresponding to the search + :param int offset: offset in results to paginate + :param int limit: limit results to paginate + :param str sort_by: column name to sort by + :param str sort_order: sort order ASC or DESC + :param dict search: the criterions of the lookup + :rtype: [sakia.data.entities.Blockchain] + """ + with self._conn: + filters = [] + values = [] + if search: + for k, v in search.items(): + filters.append("{k}=?".format(k=k)) + values.append(v) + + request = """SELECT * FROM blockchains WHERE {filters} + ORDER BY {sort_by} {sort_order} + LIMIT {limit} OFFSET {offset}""".format( + filters=" AND ".join(filters), + offset=offset, + limit=limit, + sort_by=sort_by, + sort_order=sort_order + ) + c = self._conn.execute(request, tuple(values)) + else: + request = """SELECT * FROM blockchains + ORDER BY {sort_by} {sort_order} + LIMIT {limit} OFFSET {offset}""".format( + offset=offset, + limit=limit, + sort_by=sort_by, + sort_order=sort_order + ) + c = self._conn.execute(request) + datas = c.fetchall() + if datas: + return [Blockchain(BlockchainParameters(*data[:15]), *data[16:]) for data in datas] + return [] + + def drop(self, blockchain): + """ + Drop an existing blockchain from the database + :param sakia.data.entities.Blockchain blockchain: the blockchain to update + """ + with self._conn: + where_fields = attr.astuple(blockchain, filter=attr.filters.include(*BlockchainsRepo._primary_keys)) + self._conn.execute("DELETE FROM blockchains WHERE currency=?", where_fields) diff --git a/src/sakia/data/repositories/communities.py b/src/sakia/data/repositories/communities.py deleted file mode 100644 index d2f21a1b73676c07be21d563da0374172ad1122b..0000000000000000000000000000000000000000 --- a/src/sakia/data/repositories/communities.py +++ /dev/null @@ -1,102 +0,0 @@ -import attr - -from ..entities import Community - - -@attr.s(frozen=True) -class CommunitiesRepo: - """The repository for Communities entities. - """ - _conn = attr.ib() # :type sqlite3.Connection - _primary_keys = (Community.currency,) - - def insert(self, community): - """ - Commit a community to the database - :param sakia.data.entities.Community community: the community to commit - """ - with self._conn: - community_tuple = attr.astuple(community) - values = ",".join(['?'] * len(community_tuple)) - self._conn.execute("INSERT INTO communities VALUES ({0})".format(values), community_tuple) - - def update(self, community): - """ - Update an existing community in the database - :param sakia.data.entities.Community community: the community to update - """ - with self._conn: - updated_fields = attr.astuple(community, filter=attr.filters.exclude(*CommunitiesRepo._primary_keys)) - where_fields = attr.astuple(community, filter=attr.filters.include(*CommunitiesRepo._primary_keys)) - self._conn.execute("""UPDATE communities SET - c=?, - dt=?, - ud0=?, - sig_period=?, - sig_stock=?, - sig_window=?, - sig_validity=?, - sig_qty=?, - xpercent=?, - ms_validity=?, - step_max=?, - median_time_blocks=?, - avg_gen_time=?, - dt_diff_eval=?, - blocks_rot=?, - percent_rot=? - WHERE - currency=?""", - updated_fields + where_fields) - - def get_one(self, **search): - """ - Get an existing community in the database - :param dict search: the criterions of the lookup - :rtype: sakia.data.entities.Community - """ - with self._conn: - filters = [] - values = [] - for k, v in search.items(): - filters.append("{k}=?".format(k=k)) - values.append(v) - - request = "SELECT * FROM communities WHERE {filters}".format(filters=" AND ".join(filters)) - - c = self._conn.execute(request, tuple(values)) - data = c.fetchone() - if data: - return Community(*data) - - def get_all(self, **search): - """ - Get all existing community in the database corresponding to the search - :param dict search: the criterions of the lookup - :rtype: sakia.data.entities.Community - """ - with self._conn: - filters = [] - values = [] - for k, v in search.items(): - operator = "LIKE" if k == "currency" else "=" - value = "%{value}%".format(value=v) if k == "currency" else v - filters.append("{key} {operator} ?".format(key=k, operator=operator)) - values.append(value) - - request = "SELECT * FROM communities WHERE {filters}".format(filters=" AND ".join(filters)) - - c = self._conn.execute(request, tuple(values)) - datas = c.fetchall() - if datas: - return [Community(*data) for data in datas] - return [] - - def drop(self, community): - """ - Drop an existing community from the database - :param sakia.data.entities.Community community: the community to update - """ - with self._conn: - where_fields = attr.astuple(community, filter=attr.filters.include(*CommunitiesRepo._primary_keys)) - self._conn.execute("DELETE FROM communities WHERE currency=?", where_fields) diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index 0174946638dea982ed02f4fcdbeb0d118d13450a..f30c866d442537f026272a069cc02fd8dab6076f 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -16,27 +16,34 @@ CREATE TABLE IF NOT EXISTS identities( PRIMARY KEY (currency, pubkey, uid, blockstamp) ); --- COMMUNITY TABLE -CREATE TABLE IF NOT EXISTS communities( - c FLOAT(1,6), - dt INT, - ud0 INT, - sig_period INT, - sig_stock INT, - sig_window INT, - sig_validity INT, - sig_qty INT, - xpercent FLOAT(1,6), - ms_validity INT, - step_max INT, - median_time_blocks INT, - avg_gen_time INT, - dt_diff_eval INT, - blocks_rot INT, - percent_rot FLOAT(1,6), - currency VARCHAR(30), - PRIMARY KEY (currency) - ); +-- BLOCKCHAIN TABLE +CREATE TABLE IF NOT EXISTS blockchains ( + c FLOAT(1, 6), + dt INT, + ud0 INT, + sig_period INT, + sig_stock INT, + sig_window INT, + sig_validity INT, + sig_qty INT, + xpercent FLOAT(1, 6), + ms_validity INT, + step_max INT, + median_time_blocks INT, + avg_gen_time INT, + dt_diff_eval INT, + blocks_rot INT, + percent_rot FLOAT(1, 6), + current_buid INT, + nb_members INT, + current_mass INT, + median_time INT, + last_ud INT, + last_ud_base INT, + previous_mass INT, + currency VARCHAR(30), + PRIMARY KEY (currency) +); -- CERTIFICATIONS TABLE diff --git a/src/sakia/tests/unit/data/test_blockchains_repo.py b/src/sakia/tests/unit/data/test_blockchains_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..8f831fd06dce814c07b387ab5d9e4577aff5ef11 --- /dev/null +++ b/src/sakia/tests/unit/data/test_blockchains_repo.py @@ -0,0 +1,186 @@ +import sqlite3 +import unittest + +from duniterpy.documents import BlockUID + +from sakia.data.entities import Blockchain, BlockchainParameters +from sakia.data.repositories import BlockchainsRepo, MetaDatabase + + +class TestBlockchainsRepo(unittest.TestCase): + def setUp(self): + sqlite3.register_adapter(BlockUID, str) + sqlite3.register_adapter(bool, int) + sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v))) + self.con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES) + + def tearDown(self): + self.con.close() + + def test_add_get_drop_blockchain(self): + meta_repo = MetaDatabase(self.con) + meta_repo.prepare() + meta_repo.upgrade_database() + blockchains_repo = BlockchainsRepo(self.con) + blockchains_repo.insert(Blockchain( + BlockchainParameters( + 0.1, + 86400, + 100000, + 10800, + 40, + 2629800, + 31557600, + 1, + 0.9, + 604800, + 5, + 12, + 300, + 25, + 10, + 0.66), + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 10, + 1000000, + 86400, + 100000, + 0, + 999999, + "testcurrency" + )) + blockchain = blockchains_repo.get_one(currency="testcurrency") + self.assertEqual(blockchain.parameters, BlockchainParameters( + 0.1, + 86400, + 100000, + 10800, + 40, + 2629800, + 31557600, + 1, + 0.9, + 604800, + 5, + 12, + 300, + 25, + 10, + 0.66)) + self.assertEqual(blockchain.currency, "testcurrency") + self.assertEqual(blockchain.current_buid, BlockUID(20, + "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67") + ) + self.assertEqual(blockchain.nb_members, 10) + + blockchains_repo.drop(blockchain) + blockchain = blockchains_repo.get_one(currency="testcurrency") + self.assertIsNone(blockchain) + + def test_add_get_multiple_blockchain(self): + meta_repo = MetaDatabase(self.con) + meta_repo.prepare() + meta_repo.upgrade_database() + blockchains_repo = BlockchainsRepo(self.con) + blockchains_repo.insert(Blockchain( + BlockchainParameters( + 0.1, + 86400, + 100000, + 10800, + 40, + 2629800, + 31557600, + 1, + 0.9, + 604800, + 5, + 12, + 300, + 25, + 10, + 0.66), + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 10, + 1000000, + 86400, + 100000, + 0, + 999999, + "testcurrency" + )) + blockchains_repo.insert(Blockchain( + BlockchainParameters( + 0.1, + 86400 * 365, + 100000, + 10800, + 40, + 2629800, + 31557600, + 1, + 0.9, + 604800, + 5, + 12, + 300, + 25, + 10, + 0.66), + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 20, + 1000000, + 86400, + 100000, + 0, + 999999, + "testcurrency2" + )) + + blockchains = blockchains_repo.get_all() + # result sorted by currency name by default + self.assertEquals(86400, blockchains[0].parameters.dt) + self.assertEquals("testcurrency", blockchains[0].currency) + self.assertEquals(10, blockchains[0].nb_members) + + self.assertEquals(86400*365, blockchains[1].parameters.dt) + self.assertEquals("testcurrency2", blockchains[1].currency) + self.assertEquals(20, blockchains[1].nb_members) + + def test_add_update_blockchain(self): + meta_repo = MetaDatabase(self.con) + meta_repo.prepare() + meta_repo.upgrade_database() + blockchains_repo = BlockchainsRepo(self.con) + blockchain = Blockchain( + BlockchainParameters( + 0.1, + 86400, + 100000, + 10800, + 40, + 2629800, + 31557600, + 1, + 0.9, + 604800, + 5, + 12, + 300, + 25, + 10, + 0.66), + "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 10, + 1000000, + 86400, + 100000, + 0, + 999999, + "testcurrency" + ) + blockchains_repo.insert(blockchain) + blockchain.nb_members = 30 + blockchains_repo.update(blockchain) + blockchain2 = blockchains_repo.get_one(currency="testcurrency") + self.assertEquals(30, blockchain2.nb_members) diff --git a/src/sakia/tests/unit/data/test_communities_repo.py b/src/sakia/tests/unit/data/test_communities_repo.py index 5a0d72f3b93c751b5b06ed3b865aa1c3ac39227d..fb99e2adff8ca0a727f79bb2da244d35ca35138a 100644 --- a/src/sakia/tests/unit/data/test_communities_repo.py +++ b/src/sakia/tests/unit/data/test_communities_repo.py @@ -7,7 +7,7 @@ from sakia.data.entities import Community from sakia.data.repositories import CommunitiesRepo, MetaDatabase -class TestIdentitiesRepo(unittest.TestCase): +class TestCommunitiesRepo(unittest.TestCase): def setUp(self): sqlite3.register_adapter(BlockUID, str) sqlite3.register_adapter(bool, int)