diff --git a/planetmint/backend/interfaces.py b/planetmint/backend/interfaces.py new file mode 100644 index 0000000..c20f2f0 --- /dev/null +++ b/planetmint/backend/interfaces.py @@ -0,0 +1,70 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +from dataclasses import dataclass + +# Asset should represent a single asset (e.g.: tarantool tuple (data, tx_id, asset_id)) +# If multiple assets are stored at once this should remain the same. +# For Create ({'data': 'values'}, c_tx_id, c_tx_id), For Transfer ({'id': c_tx_id}, tx_id, c_tx_id) + +@dataclass +class Asset: + id: str = None + tx_id: str = None + data: dict = None + +@dataclass +class MetaData: + id: str = None + metadata: str = None + +@dataclass +class Input: + id: str = None + +@dataclass +class Output: + id: str = None + +@dataclass +class Block: + id: str = None + app_hash: str = None + height: int = None + +@dataclass +class Script: + id: str = None + script = None + +@dataclass +class UTXO: + id: str = None + output_index: int = None + utxo: dict = None + +@dataclass +class Transaction: + id: str = None + assets: list[Asset] = None + metadata: MetaData = None + version: str = None # TODO: make enum + operation: str = None # TODO: make enum + inputs: list[Input] = None + outputs: list[Output] = None + script: str = None + +@dataclass +class Validator: + id: str = None + height: int = None + validators = None + +@dataclass +class ABCIChain: + height: str = None + is_synced: bool = None + chain_id: str = None + diff --git a/planetmint/backend/query.py b/planetmint/backend/query.py index 3ed074f..b36b787 100644 --- a/planetmint/backend/query.py +++ b/planetmint/backend/query.py @@ -7,11 +7,10 @@ from functools import singledispatch from planetmint.backend.exceptions import OperationError +from planetmint.backend.interfaces import Asset, MetaData - -# FIXME ADD HERE HINT FOR RETURNING TYPE @singledispatch -def store_asset(asset: dict, connection): +def store_asset(connection, asset: dict) -> Asset: """Write an asset to the asset table. Args: @@ -25,7 +24,7 @@ def store_asset(asset: dict, connection): @singledispatch -def store_assets(assets: list, connection): +def store_assets(connection, assets: list) -> list[Asset]: """Write a list of assets to the assets table. backend Args: @@ -39,7 +38,7 @@ def store_assets(assets: list, connection): @singledispatch -def store_metadatas(connection, metadata): +def store_metadatas(connection, metadata) -> MetaData: """Write a list of metadata to metadata table. Args: @@ -88,7 +87,7 @@ def get_transactions(connection, transaction_ids): @singledispatch -def get_asset(connection, asset_id): +def get_asset(connection, asset_id) -> Asset: """Get an asset from the assets table. Args: @@ -191,7 +190,7 @@ def get_metadata(connection, transaction_ids): @singledispatch -def get_assets(connection, asset_ids) -> list: +def get_assets(connection, asset_ids) -> list[Asset]: """Get a list of assets from the assets table. Args: asset_ids (list): a list of ids for the assets to be retrieved from diff --git a/planetmint/backend/tarantool/query.py b/planetmint/backend/tarantool/query.py index 8eae34a..1d0f054 100644 --- a/planetmint/backend/tarantool/query.py +++ b/planetmint/backend/tarantool/query.py @@ -12,10 +12,10 @@ from operator import itemgetter from tarantool.error import DatabaseError from planetmint.backend import query from planetmint.backend.utils import module_dispatch_registrar +from planetmint.backend.interfaces import Asset, MetaData from planetmint.backend.tarantool.connection import TarantoolDBConnection from planetmint.backend.tarantool.transaction.tools import TransactionCompose, TransactionDecompose - register_query = module_dispatch_registrar(query) @@ -91,11 +91,11 @@ def get_transactions(connection, transactions_ids: list): @register_query(TarantoolDBConnection) -def store_metadatas(connection, metadata: list): +def store_metadatas(connection, metadata: list[MetaData]): for meta in metadata: connection.run( connection.space("meta_data").insert( - (meta["id"], json.dumps(meta["data"] if not "metadata" in meta else meta["metadata"])) + (meta.id, json.dumps(meta.metadata)) ) # noqa: E713 ) @@ -115,17 +115,10 @@ def get_metadata(connection, transaction_ids: list): @register_query(TarantoolDBConnection) -def store_asset(connection, asset): - def convert(obj): - if isinstance(obj, tuple): - obj = list(obj) - obj[0] = json.dumps(obj[0]) - return tuple(obj) - else: - return (json.dumps(obj), obj["id"], obj["id"]) - +def store_asset(connection, asset: Asset): + asset = (json.dumps(asset.data), asset.tx_id, asset.id) try: - return connection.run(connection.space("assets").insert(convert(asset)), only_data=False) + return connection.run(connection.space("assets").insert(asset), only_data=False) except DatabaseError: pass @@ -137,21 +130,20 @@ def store_assets(connection, assets: list): @register_query(TarantoolDBConnection) -def get_asset(connection, asset_id: str): +def get_asset(connection, asset_id: str) -> Asset: _data = connection.run(connection.space("assets").select(asset_id, index="txid_search")) - - return json.loads(_data[0][0]) if len(_data) > 0 else [] + return Asset(_data[0][2], _data[0][1], json.loads(_data[0][0])) @register_query(TarantoolDBConnection) -def get_assets(connection, assets_ids: list) -> list: +def get_assets(connection, assets_ids: list) -> list[Asset]: _returned_data = [] for _id in list(set(assets_ids)): res = connection.run(connection.space("assets").select(_id, index="txid_search")) _returned_data.append(res[0]) sorted_assets = sorted(_returned_data, key=lambda k: k[1], reverse=False) - return [(json.loads(asset[0]), asset[1]) for asset in sorted_assets] + return [Asset(asset[2], asset[1], json.loads(asset[0])) for asset in sorted_assets] @register_query(TarantoolDBConnection) @@ -238,7 +230,7 @@ def text_search(conn, search, table="assets", limit=0): if len(res[0]): # NEEDS BEAUTIFICATION if table == "assets": for result in res[0]: - to_return.append({"data": json.loads(result[0])[0]["data"], "id": result[1]}) + to_return.append({"data": json.loads(result[0])["data"], "id": result[1]}) else: for result in res[0]: to_return.append({"metadata": json.loads(result[1]), "id": result[0]}) diff --git a/planetmint/lib.py b/planetmint/lib.py index 912a94c..10271db 100644 --- a/planetmint/lib.py +++ b/planetmint/lib.py @@ -54,6 +54,7 @@ from planetmint.tendermint_utils import ( ) from planetmint import exceptions as core_exceptions from planetmint.validation import BaseValidationRules +from planetmint.backend.interfaces import Asset, MetaData logger = logging.getLogger(__name__) @@ -148,21 +149,16 @@ class Planetmint(object): tx_assets = transaction.pop("assets") metadata = transaction.pop("metadata") - - tx_assets = backend.convert.prepare_asset( - self.connection, - transaction_type=transaction["operation"], - transaction_id=transaction["id"], - filter_operation=[t.CREATE, t.VALIDATOR_ELECTION, t.CHAIN_MIGRATION_ELECTION], - assets=tx_assets, - ) - - metadata = backend.convert.prepare_metadata( - self.connection, transaction_id=transaction["id"], metadata=metadata - ) + + if tx_assets is not None: + for asset in tx_assets: + id = transaction["id"] if "id" not in asset else asset["id"] + tx_asset = Asset(id, transaction["id"], asset) + assets.append(tx_asset) + + metadata = MetaData(transaction["id"], metadata) txn_metadatas.append(metadata) - assets.append(tx_assets) txns.append(transaction) backend.query.store_metadatas(self.connection, txn_metadatas) @@ -258,10 +254,7 @@ class Planetmint(object): if transaction: assets = backend.query.get_assets(self.connection, [transaction_id]) metadata = backend.query.get_metadata(self.connection, [transaction_id]) - # NOTE: assets must not be replaced for transfer transactions - # NOTE: assets should be appended for all txs that define new assets otherwise the ids are already stored in tx - if transaction["operation"] != "TRANSFER" and transaction["operation"] != "VOTE" and assets: - transaction["assets"] = assets[0][0] + transaction["assets"] = [asset.data for asset in assets] if "metadata" not in transaction: metadata = metadata[0] if metadata else None @@ -323,6 +316,8 @@ class Planetmint(object): raise DoubleSpend('tx "{}" spends inputs twice'.format(txid)) elif transactions: transaction = backend.query.get_transaction(self.connection, transactions[0]["id"]) + assets = backend.query.get_assets(self.connection, [transaction["id"]]) + transaction["assets"] = [asset.data for asset in assets] transaction = Transaction.from_dict(transaction, False) elif current_spent_transactions: transaction = current_spent_transactions[0] @@ -476,7 +471,7 @@ class Planetmint(object): """ return backend.query.text_search(self.connection, search, limit=limit, table=table) - def get_assets(self, asset_ids): + def get_assets(self, asset_ids) -> list[Asset]: """Return a list of assets that match the asset_ids Args: @@ -597,12 +592,12 @@ class Planetmint(object): tx_map[tx["id"]] = tx tx_ids.append(tx["id"]) - assets = list(self.get_assets(tx_ids)) + # TODO: this will break if more than one asset is used + assets = self.get_assets(tx_ids) for asset in assets: if asset is not None: - # This is tarantool specific behaviour needs to be addressed - tx = tx_map[asset[1]] - tx["asset"] = asset[0] + tx = tx_map[asset.id] + tx["assets"] = [asset.data] tx_ids = list(tx_map.keys()) metadata_list = list(self.get_metadata(tx_ids)) diff --git a/setup.py b/setup.py index aecfb9f..2531982 100644 --- a/setup.py +++ b/setup.py @@ -137,7 +137,7 @@ install_requires = [ "pyasn1>=0.4.8", "cryptography==3.4.7", "python-decouple", - "planetmint-transactions==0.2.0", + "planetmint-transactions==0.2.1", ] setup( diff --git a/tests/backend/tarantool/test_queries.py b/tests/backend/tarantool/test_queries.py index 74e6041..9c704e1 100644 --- a/tests/backend/tarantool/test_queries.py +++ b/tests/backend/tarantool/test_queries.py @@ -10,6 +10,7 @@ import json from transactions.common.transaction import Transaction from transactions.types.assets.create import Create from transactions.types.assets.transfer import Transfer +from planetmint.backend.interfaces import Asset, MetaData pytestmark = pytest.mark.bdb @@ -44,11 +45,11 @@ def test_write_assets(db_conn): from planetmint.backend.tarantool import query assets = [ - {"id": "1", "data": "1"}, - {"id": "2", "data": "2"}, - {"id": "3", "data": "3"}, + Asset("1", "1", "1"), + Asset("2", "2", "2"), + Asset("3", "3", "3"), # Duplicated id. Should not be written to the database - {"id": "1", "data": "1"}, + Asset("1", "1", "1") ] # write the assets @@ -56,25 +57,25 @@ def test_write_assets(db_conn): query.store_asset(connection=db_conn, asset=asset) # check that 3 assets were written to the database - documents = query.get_assets(assets_ids=[asset["id"] for asset in assets], connection=db_conn) + documents = query.get_assets(assets_ids=[asset.id for asset in assets], connection=db_conn) assert len(documents) == 3 - assert list(documents)[0][0] == assets[:-1][0] + assert list(documents)[0] == assets[:-1][0] def test_get_assets(db_conn): from planetmint.backend.tarantool import query - + assets = [ - ("1", "1", "1"), - ("2", "2", "2"), - ("3", "3", "3"), + Asset("1", "1"), + Asset("2", "2"), + Asset("3", "3"), ] query.store_assets(assets=assets, connection=db_conn) for asset in assets: - assert query.get_asset(asset_id=asset[2], connection=db_conn) + assert query.get_asset(asset_id=asset.id, connection=db_conn) @pytest.mark.parametrize("table", ["assets", "metadata"]) @@ -164,17 +165,17 @@ def test_text_search(table): def test_write_metadata(db_conn): from planetmint.backend.tarantool import query - metadata = [{"id": "1", "data": "1"}, {"id": "2", "data": "2"}, {"id": "3", "data": "3"}] + metadata = [MetaData("1", "1"), MetaData("2", "2"), MetaData("3", "3")] # write the assets query.store_metadatas(connection=db_conn, metadata=metadata) # check that 3 assets were written to the database metadatas = [] for meta in metadata: - _data = db_conn.run(db_conn.space("meta_data").select(meta["id"]))[0] - metadatas.append({"id": _data[0], "data": json.loads(_data[1])}) + _data = db_conn.run(db_conn.space("meta_data").select(meta.id))[0] + metadatas.append(MetaData(_data[0], json.loads(_data[1]))) - metadatas = sorted(metadatas, key=lambda k: k["id"]) + metadatas = sorted(metadatas, key=lambda k: k.id) assert len(metadatas) == 3 assert list(metadatas) == metadata @@ -184,15 +185,15 @@ def test_get_metadata(db_conn): from planetmint.backend.tarantool import query metadata = [ - {"id": "dd86682db39e4b424df0eec1413cfad65488fd48712097c5d865ca8e8e059b64", "metadata": None}, - {"id": "55a2303e3bcd653e4b5bd7118d39c0e2d48ee2f18e22fbcf64e906439bdeb45d", "metadata": {"key": "value"}}, + MetaData("dd86682db39e4b424df0eec1413cfad65488fd48712097c5d865ca8e8e059b64", None), + MetaData("55a2303e3bcd653e4b5bd7118d39c0e2d48ee2f18e22fbcf64e906439bdeb45d", {"key": "value"}) ] # conn.db.metadata.insert_many(deepcopy(metadata), ordered=False) query.store_metadatas(connection=db_conn, metadata=metadata) for meta in metadata: - _m = query.get_metadata(connection=db_conn, transaction_ids=[meta["id"]]) + _m = query.get_metadata(connection=db_conn, transaction_ids=[meta.id]) assert _m diff --git a/tests/tendermint/test_lib.py b/tests/tendermint/test_lib.py index 44e2a57..93a61ab 100644 --- a/tests/tendermint/test_lib.py +++ b/tests/tendermint/test_lib.py @@ -20,6 +20,7 @@ from transactions.common.transaction_mode_types import ( BROADCAST_TX_SYNC, ) from planetmint.lib import Block +from planetmint.backend.interfaces import Asset, MetaData from ipld import marshal, multihash @@ -195,12 +196,12 @@ def test_store_transaction(mocker, b, signed_create_tx, signed_transfer_tx, db_c ) else: mocked_store_asset.assert_called_once_with( - b.connection, [(signed_create_tx.assets, signed_create_tx.id, signed_create_tx.id)] + b.connection, [Asset(signed_create_tx.id, signed_create_tx.id, signed_create_tx.assets[0])] ) mocked_store_metadata.assert_called_once_with( b.connection, - [{"id": signed_create_tx.id, "metadata": signed_create_tx.metadata}], + [MetaData(signed_create_tx.id, signed_create_tx.metadata)] ) mocked_store_transaction.assert_called_once_with( b.connection, @@ -245,7 +246,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx, signed_transfer_tx, if isinstance(b.connection, TarantoolDBConnection): mocked_store_assets.assert_called_once_with( b.connection, # signed_create_tx.asset['data'] this was before - [(signed_create_tx.assets, signed_create_tx.id, signed_create_tx.id)], + [Asset(signed_create_tx.id, signed_create_tx.id, signed_create_tx.assets[0])] ) else: mocked_store_assets.assert_called_once_with( @@ -254,7 +255,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx, signed_transfer_tx, ) mocked_store_metadata.assert_called_once_with( b.connection, - [{"id": signed_create_tx.id, "metadata": signed_create_tx.metadata}], + [MetaData(signed_create_tx.id, signed_create_tx.metadata)] ) mocked_store_transactions.assert_called_once_with( b.connection, @@ -513,28 +514,20 @@ def test_migrate_abci_chain_generates_new_chains(b, chain, block_height, expecte @pytest.mark.bdb def test_get_spent_key_order(b, user_pk, user_sk, user2_pk, user2_sk): - from planetmint import backend from transactions.common.crypto import generate_key_pair from transactions.common.exceptions import DoubleSpend alice = generate_key_pair() bob = generate_key_pair() - tx1 = Create.generate([user_pk], [([alice.public_key], 3), ([user_pk], 2)], assets=None).sign([user_sk]) + tx1 = Create.generate([user_pk], [([alice.public_key], 3), ([user_pk], 2)]).sign([user_sk]) b.store_bulk_transactions([tx1]) inputs = tx1.to_inputs() tx2 = Transfer.generate([inputs[1]], [([user2_pk], 2)], [tx1.id]).sign([user_sk]) assert b.validate_transaction(tx2) - - tx2_dict = tx2.to_dict() - fulfills = tx2_dict["inputs"][0]["fulfills"] - tx2_dict["inputs"][0]["fulfills"] = { - "output_index": fulfills["output_index"], - "transaction_id": fulfills["transaction_id"], - } - - backend.query.store_transactions(b.connection, [tx2_dict]) + + b.store_bulk_transactions([tx2]) tx3 = Transfer.generate([inputs[1]], [([bob.public_key], 2)], [tx1.id]).sign([user_sk])