diff --git a/planetmint/backend/interfaces.py b/planetmint/backend/interfaces.py index 2277565..449f3fa 100644 --- a/planetmint/backend/interfaces.py +++ b/planetmint/backend/interfaces.py @@ -4,7 +4,7 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 from dataclasses import dataclass - + # NOTE: only here temporarily from planetmint.backend.models import Asset, MetaData, Input from planetmint.backend.models import Output @@ -16,37 +16,41 @@ class Block: 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 + 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/models/__init__.py b/planetmint/backend/models/__init__.py index d8d99e3..c102251 100644 --- a/planetmint/backend/models/__init__.py +++ b/planetmint/backend/models/__init__.py @@ -9,3 +9,5 @@ from .input import Input from .metadata import MetaData from .script import Script from .output import Output +from .keys import Keys +from .dbtransaction import DbTransaction diff --git a/planetmint/backend/models/asset.py b/planetmint/backend/models/asset.py index 6dc120c..d1b3674 100644 --- a/planetmint/backend/models/asset.py +++ b/planetmint/backend/models/asset.py @@ -7,12 +7,24 @@ from __future__ import annotations import json from dataclasses import dataclass + @dataclass class Asset: id: str = "" tx_id: str = "" - data: str = "" - + data: dict = "" + @staticmethod def from_tuple(asset_tuple: tuple) -> Asset: - return Asset(asset_tuple[2], asset_tuple[1], json.loads(asset_tuple[0])) \ No newline at end of file + return Asset(asset_tuple[2], asset_tuple[1], json.loads(asset_tuple[0])["data"]) + + def to_dict(self) -> dict: + return { + "id": self.id, + "tx_id": self.tx_id, + "data": self.data + } + + @staticmethod + def list_to_dict(asset_list: list[Asset]) -> list[dict]: + return [asset.to_dict() for asset in asset_list] diff --git a/planetmint/backend/models/dbtransaction.py b/planetmint/backend/models/dbtransaction.py new file mode 100644 index 0000000..9022aa3 --- /dev/null +++ b/planetmint/backend/models/dbtransaction.py @@ -0,0 +1,57 @@ +# 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 __future__ import annotations +from dataclasses import dataclass + +from planetmint.backend.models import Asset, MetaData, Input, Output, Script +from planetmint.backend.models.keys import Keys + + +@dataclass +class DbTransaction: + id: str = "" + operation: str = "" + version: str = "" + raw_transaction: dict = dict + assets: list[Asset] = None + metadata: MetaData = None + inputs: list[Input] = None + outputs: list[Output] = None + keys: Keys = None + script: Script = None + + @staticmethod + def from_dict(transaction: dict) -> DbTransaction: + return DbTransaction( + id=transaction["id"], + operation=transaction["operation"], + version=transaction["version"], + inputs=transaction["inputs"], + raw_transaction=transaction["transaction"], + ) + + @staticmethod + def from_tuple(transaction: tuple) -> DbTransaction: + return DbTransaction( + id=transaction[0], + operation=transaction[1], + version=transaction[2], + raw_transaction=transaction[3], + ) + + def to_dict(self) -> dict: + return { + "id": self.id, + "operation": self.operation, + "version": self.version, + "inputs": Input.list_to_dict(self.inputs), + "assets": Asset.list_to_dict(self.assets), + "metadata": self.metadata.to_dict(), + "outputs": Output.list_to_dict(self.outputs), + "keys": self.keys.to_dict(), + "script": self.script.to_dict(), + "transaction": self.raw_transaction, + } diff --git a/planetmint/backend/models/fulfills.py b/planetmint/backend/models/fulfills.py index 096a178..ae072be 100644 --- a/planetmint/backend/models/fulfills.py +++ b/planetmint/backend/models/fulfills.py @@ -5,7 +5,8 @@ from dataclasses import dataclass + @dataclass class Fulfills: transaction_id: str = "" - output_index: int = 0 \ No newline at end of file + output_index: int = 0 diff --git a/planetmint/backend/models/input.py b/planetmint/backend/models/input.py index fcc0491..51047a7 100644 --- a/planetmint/backend/models/input.py +++ b/planetmint/backend/models/input.py @@ -9,25 +9,23 @@ from typing import Optional from .fulfills import Fulfills + @dataclass class Input: tx_id: str = "" fulfills: Optional[Fulfills] = None owners_before: list[str] = field(default_factory=list) fulfillment: str = "" - + @staticmethod def from_dict(input_dict: dict, tx_id: str = "") -> Input: fulfills = None - + if input_dict["fulfills"]: - fulfills = Fulfills( - input_dict["fulfills"]["transaction_id"], - input_dict["fulfills"]["output_index"] - ) - + fulfills = Fulfills(input_dict["fulfills"]["transaction_id"], input_dict["fulfills"]["output_index"]) + return Input(tx_id, fulfills, input_dict["owners_before"], input_dict["fulfillment"]) - + @staticmethod def from_tuple(input_tuple: tuple) -> Input: tx_id = input_tuple[0] @@ -39,17 +37,20 @@ class Input: if fulfills_tx_id: # TODO: the output_index should be an unsigned int fulfills = Fulfills(fulfills_tx_id, int(input_tuple[4])) - + return Input(tx_id, fulfills, owners_before, fulfillment) - - def to_input_dict(self) -> dict: - fulfills = { - "transaction_id": self.fulfills.transaction_id, - "output_index": self.fulfills.output_index - } if self.fulfills else None - - return { - "fulfills": fulfills, - "fulfillment": self.fulfillment, - "owners_before": self.owners_before - } \ No newline at end of file + + def to_dict(self) -> dict: + fulfills = ( + {"transaction_id": self.fulfills.transaction_id, "output_index": self.fulfills.output_index} + if self.fulfills + else None + ) + + return {"fulfills": fulfills, "fulfillment": self.fulfillment, "owners_before": self.owners_before} + + @staticmethod + def list_to_dict(input_list: list[Input]) -> list[dict]: + return [input.to_dict() for input in input_list] + + diff --git a/planetmint/backend/models/keys.py b/planetmint/backend/models/keys.py index 063b032..1032061 100644 --- a/planetmint/backend/models/keys.py +++ b/planetmint/backend/models/keys.py @@ -7,6 +7,7 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import Optional + @dataclass class Keys: tx_id: str = "" @@ -20,7 +21,6 @@ class Keys: public_keys=output["public_keys"], ) - @staticmethod def from_tuple(output: tuple) -> Keys: return Keys( diff --git a/planetmint/backend/models/metadata.py b/planetmint/backend/models/metadata.py index c645a63..c362a3f 100644 --- a/planetmint/backend/models/metadata.py +++ b/planetmint/backend/models/metadata.py @@ -8,11 +8,18 @@ import json from dataclasses import dataclass from typing import Optional + @dataclass class MetaData: id: str = "" metadata: Optional[str] = None - + @staticmethod def from_tuple(meta_data_tuple: tuple) -> MetaData: - return MetaData(meta_data_tuple[0], json.loads(meta_data_tuple[1])) \ No newline at end of file + return MetaData(meta_data_tuple[0], json.loads(meta_data_tuple[1])) + + def to_dict(self) -> dict: + return { + "id": self.id, + "metadata": self.metadata + } diff --git a/planetmint/backend/models/output.py b/planetmint/backend/models/output.py index 4ea8c82..b8e72b5 100644 --- a/planetmint/backend/models/output.py +++ b/planetmint/backend/models/output.py @@ -37,8 +37,9 @@ class Condition: @dataclass class Output: + id: str = "" tx_id: str = "" - amount: int = 0 + amount: str = '0' public_keys: List[str] = field(default_factory=list) condition: Condition = field(default_factory=Condition) @@ -54,6 +55,7 @@ class Output: @staticmethod def from_tuple(output: tuple) -> Output: return Output( + id=output[5], tx_id=output[0], amount=output[1], condition=Condition( @@ -67,7 +69,7 @@ class Output: ), ) - def to_output_dict(self) -> dict: + def to_dict(self) -> dict: return { "id": self.tx_id, "amount": self.amount, @@ -83,6 +85,10 @@ class Output: }, } + @staticmethod + def list_to_dict(output_list: list[Output]) -> list[dict]: + return [output.to_dict() for output in output_list] + def output_with_public_key(output, tx_id) -> Output: return Output( @@ -114,13 +120,8 @@ def output_with_sub_conditions(output, tx_id) -> Output: type=sub_condition["type"], public_key=sub_condition["public_key"], ) - for sub_condition in output["condition"]["details"][ - "subconditions" - ] + for sub_condition in output["condition"]["details"]["subconditions"] ], ), ), ) - - - diff --git a/planetmint/backend/models/script.py b/planetmint/backend/models/script.py index ce26968..682a728 100644 --- a/planetmint/backend/models/script.py +++ b/planetmint/backend/models/script.py @@ -7,13 +7,18 @@ from __future__ import annotations from dataclasses import dataclass from typing import Optional + @dataclass class Script: id: str = "" script: Optional[str] = None - + @staticmethod def from_tuple(script_tuple: tuple) -> Script: return Script(script_tuple[0], script_tuple[1]) - - + + def to_dict(self) -> dict: + return { + "id": self.id, + "script": self.script + } diff --git a/planetmint/backend/models/transaction.py b/planetmint/backend/models/transaction.py deleted file mode 100644 index 0db0ca4..0000000 --- a/planetmint/backend/models/transaction.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 __future__ import annotations -from dataclasses import dataclass, field -from typing import Optional - -@dataclass -class Transaction: - id: str = "" - operation: str = "" - version: str = "" - - @staticmethod - def from_dict(transaction: dict) -> Transaction: - return Transaction( - id=transaction["id"], - operation=transaction["operation"], - version=transaction["version"], - ) - - - @staticmethod - def from_tuple(transaction: tuple) -> Transaction: - return Transaction( - id=transaction[0], - operation=transaction[1], - version=transaction[2], - ) - - def to_dict(self) -> dict: - return { - "id": self.id, - "operation": self.operation, - "version": self.version, - } diff --git a/planetmint/backend/query.py b/planetmint/backend/query.py index 7d3abbd..5d7ddc9 100644 --- a/planetmint/backend/query.py +++ b/planetmint/backend/query.py @@ -6,9 +6,13 @@ """Query interfaces for backends.""" from functools import singledispatch + +from planetmint.backend.models import Asset, MetaData, Output, Input, Script + from planetmint.backend.exceptions import OperationError -from planetmint.backend.interfaces import Asset, Block, MetaData, Input, Script, Output, Transaction from planetmint.backend.models.keys import Keys +from planetmint.backend.interfaces import Block +from planetmint.backend.models.dbtransaction import DbTransaction @singledispatch @@ -59,6 +63,7 @@ def store_transactions(connection, signed_transactions): raise NotImplementedError + @singledispatch def store_transaction(connection, transaction): """Store a single transaction.""" @@ -67,14 +72,27 @@ def store_transaction(connection, transaction): @singledispatch -def get_transaction(conn, transaction_id): - """Get a transaction from the database.""" +def get_transaction_space_by_id(connection, transaction_id): + """Get the transaction space by transaction id.""" raise NotImplementedError @singledispatch -def get_transactions(connection, transactions_ids) -> list[Transaction]: +def get_transaction_single(connection, transaction_id) -> DbTransaction: + """Get a single transaction by id.""" + + raise NotImplementedError + + +@singledispatch +def get_transaction(connection, transaction_id): + """Get a transaction by id.""" + + raise NotImplementedError + +@singledispatch +def get_transactions(connection, transactions_ids) -> list[DbTransaction]: """Get a transaction from the transactions table. Args: @@ -101,6 +119,20 @@ def get_asset(connection, asset_id) -> Asset: raise NotImplementedError +@singledispatch +def get_assets_by_tx_id(connection, tx_id: str) -> list[Asset]: + """Get assets by transaction id. + + Args: + tx_id (str): the id of the transaction. + + Returns: + The result of the operation. + """ + + raise NotImplementedError + + @singledispatch def get_spent(connection, transaction_id, condition_id): """Check if a `txid` was already used as an input. @@ -175,6 +207,7 @@ def get_block_with_transaction(connection, txid): raise NotImplementedError + @singledispatch def get_metadata_by_tx_id(connection, transaction_id: str) -> MetaData: """Get metadata from the metadata table containing `transaction_id`. @@ -188,6 +221,7 @@ def get_metadata_by_tx_id(connection, transaction_id: str) -> MetaData: """ raise NotImplementedError + @singledispatch def store_transaction_outputs_and_keys(connection, output: Output, index: int): """Store the transaction outputs. @@ -209,6 +243,7 @@ def store_transaction_outputs(connection, output: Output, index: int): """ raise NotImplementedError + @singledispatch def store_transaction_keys(connection, keys: [Keys], output_id: str, index: int): """Store the transaction keys. @@ -480,21 +515,25 @@ def get_latest_abci_chain(conn): """ raise NotImplementedError + @singledispatch def get_inputs_by_tx_id(connection, tx_id) -> list[Input]: """Retrieve inputs for a transaction by its id""" raise NotImplementedError + @singledispatch def store_transaction_inputs(connection, inputs: list[Input]): """Store inputs for a transaction""" raise NotImplementedError + @singledispatch def _group_transaction_by_ids(txids: list, connection): """Returns the transactions object (JSON TYPE), from list of ids.""" raise NotImplementedError + @singledispatch def get_script_by_tx_id(connection, tx_id: str) -> Script: """Retrieve script for a transaction by its id""" @@ -506,7 +545,8 @@ def get_outputs_by_tx_id(connection, tx_id: str) -> list[Output]: """Retrieve outputs for a transaction by its id""" raise NotImplementedError + @singledispatch def get_keys_by_tx_id(connection, tx_id: str) -> list[Keys]: """Retrieve keys for a transaction by its id""" - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/planetmint/backend/tarantool/query.py b/planetmint/backend/tarantool/query.py index 9453295..6a65830 100644 --- a/planetmint/backend/tarantool/query.py +++ b/planetmint/backend/tarantool/query.py @@ -11,10 +11,18 @@ from operator import itemgetter from tarantool.error import DatabaseError from planetmint.backend import query from planetmint.backend.models.keys import Keys -from planetmint.backend.models.transaction import Transaction -from planetmint.backend.tarantool.const import TARANT_TABLE_META_DATA, TARANT_TABLE_ASSETS, TARANT_TABLE_KEYS, \ - TARANT_TABLE_TRANSACTION, TARANT_TABLE_INPUT, TARANT_TABLE_OUTPUT, TARANT_TABLE_SCRIPT, TARANT_TX_ID_SEARCH, \ - TARANT_ID_SEARCH +from planetmint.backend.models.dbtransaction import DbTransaction +from planetmint.backend.tarantool.const import ( + TARANT_TABLE_META_DATA, + TARANT_TABLE_ASSETS, + TARANT_TABLE_KEYS, + TARANT_TABLE_TRANSACTION, + TARANT_TABLE_INPUT, + TARANT_TABLE_OUTPUT, + TARANT_TABLE_SCRIPT, + TARANT_TX_ID_SEARCH, + TARANT_ID_SEARCH, +) from planetmint.backend.utils import module_dispatch_registrar from planetmint.backend.models import Asset, MetaData, Input, Script, Output from planetmint.backend.tarantool.connection import TarantoolDBConnection @@ -23,35 +31,29 @@ register_query = module_dispatch_registrar(query) @register_query(TarantoolDBConnection) -def _group_transaction_by_ids(connection, txids: list): +def _group_transaction_by_ids(connection, txids: list) -> list[DbTransaction]: _transactions = [] for txid in txids: - _txobject = connection.run(connection.space(TARANT_TABLE_TRANSACTION).get(txid, index=TARANT_ID_SEARCH)) - - if _txobject is None: + tx = get_transaction_space_by_id(connection, txid) + if tx is None: continue - _txinputs = get_inputs_by_tx_id(connection, txid) - _txoutputs = get_outputs_by_tx_id(connection, txid) - _txkeys = get_keys_by_tx_id(connection, txid) - _txassets = get_assets(connection, [txid]) - _txmeta = get_metadata_by_tx_id(connection, txid) - _txscript = get_script_by_tx_id(connection, txid) + tx.inputs = get_inputs_by_tx_id(connection, txid) + _output = get_outputs_by_tx_id(connection, txid) + _keys = get_keys_by_tx_id(connection, txid) + tx.outputs = [_enricht_output_with_public_keys(_keys, output) for output in _output] + tx.assets = get_assets_by_tx_id(connection, txid) + tx.metadata = get_metadata_by_tx_id(connection, txid) + tx.script = get_script_by_tx_id(connection, txid) - _transaction = get_transaction(connection, txid) - _transaction[TARANT_TABLE_TRANSACTION] = [tx.to_dict for tx in _transactions] - _transaction[TARANT_TABLE_INPUT] + [input.to_input_dict() for input in _txinputs] - _transaction[TARANT_TABLE_OUTPUT] = [output.to_output_dict() for output in _txoutputs] - _transaction[TARANT_TABLE_KEYS] = [key.to_dict() for key in _txkeys] - _transaction["assets"] = [asset.data for asset in _txassets] - _transaction["metadata"] = _txmeta.metadata - - if _txscript.script: - _transaction[TARANT_TABLE_SCRIPT] = _txscript.script - _transactions.append(_transaction) + _transactions.append(tx) return _transactions +def _enricht_output_with_public_keys(keys: list[Keys], output: Output) -> Output: + output.public_keys = [key.public_keys for key in keys if key.output_id == output.id] + return output + @register_query(TarantoolDBConnection) def get_inputs_by_tx_id(connection, tx_id: str) -> list[Input]: _inputs = connection.run(connection.space(TARANT_TABLE_INPUT).select(tx_id, index=TARANT_ID_SEARCH)) @@ -73,18 +75,26 @@ def get_keys_by_tx_id(connection, tx_id: str) -> list[Keys]: return [Keys.from_tuple(key) for key in _sorted_keys] +@register_query(TarantoolDBConnection) +def get_transaction(connection, tx_id: str) -> DbTransaction: + return NotImplemented + @register_query(TarantoolDBConnection) def store_transaction_inputs(connection, input: Input, index: int): - connection.run(connection.space(TARANT_TABLE_INPUT).insert(( - input.tx_id, - input.fulfillment, - input.owners_before, - input.fulfills.transaction_id if input.fulfills else "", - # TODO: the output_index should be an unsigned int - str(input.fulfills.output_index) if input.fulfills else "", - uuid4().hex, - index - ))) + connection.run( + connection.space(TARANT_TABLE_INPUT).insert( + ( + input.tx_id, + input.fulfillment, + input.owners_before, + input.fulfills.transaction_id if input.fulfills else "", + # TODO: the output_index should be an unsigned int + str(input.fulfills.output_index) if input.fulfills else "", + uuid4().hex, + index, + ) + ) + ) @register_query(TarantoolDBConnection) @@ -120,34 +130,32 @@ def store_transaction_outputs(connection, output: Output, index: int) -> str: index, ) - connection.run(connection.space(TARANT_TABLE_OUTPUT).insert(( - tmp_output - ))) + connection.run(connection.space(TARANT_TABLE_OUTPUT).insert((tmp_output))) return output_id @register_query(TarantoolDBConnection) def store_transaction_keys(connection, keys: Keys, output_id: str, index: int): for key in keys.public_keys: - connection.run(connection.space(TARANT_TABLE_KEYS).insert(( - uuid4().hex, - keys.tx_id, - output_id, - key, - index - ))) + connection.run(connection.space(TARANT_TABLE_KEYS).insert((uuid4().hex, keys.tx_id, output_id, key, index))) @register_query(TarantoolDBConnection) def store_transactions(connection, signed_transactions: list): for transaction in signed_transactions: + store_transaction(connection, transaction) - [store_transaction_inputs(connection, Input.from_dict(input, transaction["id"]), index) for - index, input in enumerate(transaction[TARANT_TABLE_INPUT])] + [ + store_transaction_inputs(connection, Input.from_dict(input, transaction["id"]), index) + for index, input in enumerate(transaction[TARANT_TABLE_INPUT]) + ] - [store_transaction_outputs_and_keys(connection, Output.outputs_and_keys_dict(output, transaction["id"]), index) - for index, output in - enumerate(transaction[TARANT_TABLE_OUTPUT])] + [ + store_transaction_outputs_and_keys( + connection, Output.outputs_and_keys_dict(output, transaction["id"]), index + ) + for index, output in enumerate(transaction[TARANT_TABLE_OUTPUT]) + ] store_metadatas(connection, [MetaData(transaction["id"], transaction["metadata"])]) @@ -160,39 +168,39 @@ def store_transactions(connection, signed_transactions: list): if TARANT_TABLE_SCRIPT in transaction: connection.run( connection.space(TARANT_TABLE_SCRIPT).insert((transaction["id"], transaction[TARANT_TABLE_SCRIPT])), - only_data=False) + only_data=False, + ) @register_query(TarantoolDBConnection) def store_transaction(connection, transaction): - tx = Transaction(id=transaction["id"], operation=transaction["operation"], version=transaction["version"]) - connection.run(connection.space(TARANT_TABLE_TRANSACTION).insert( - tx.id, - tx.operation, - tx.version, - ), - only_data=False) + tx = (transaction["id"], transaction["operation"], transaction["version"], [transaction]) + connection.run(connection.space(TARANT_TABLE_TRANSACTION).insert(tx), only_data=False) @register_query(TarantoolDBConnection) -def get_transaction(connection, transaction_id: str) -> Transaction: - return Transaction.from_tuple( - connection.run(connection.space(TARANT_TABLE_TRANSACTION).get(transaction_id, index=TARANT_ID_SEARCH))) +def get_transaction_space_by_id(connection, transaction_id): + txs = connection.run(connection.space(TARANT_TABLE_TRANSACTION).select(transaction_id, index=TARANT_ID_SEARCH)) + if len(txs) == 0: + return None + return DbTransaction.from_tuple(txs[0]) @register_query(TarantoolDBConnection) -def get_transactions(connection, transactions_ids: list) -> list[Transaction]: - _transactions = _group_transaction_by_ids(txids=transactions_ids, connection=connection) - return [Transaction.from_tuple(_transaction) for _transaction in _transactions] +def get_transaction_single(connection, transaction_id) -> DbTransaction: + return _group_transaction_by_ids(txids=[transaction_id], connection=connection)[0] + + +@register_query(TarantoolDBConnection) +def get_transactions(connection, transactions_ids: list) -> list[DbTransaction]: + return _group_transaction_by_ids(txids=transactions_ids, connection=connection) @register_query(TarantoolDBConnection) def store_metadatas(connection, metadata: list[MetaData]): for meta in metadata: connection.run( - connection.space(TARANT_TABLE_META_DATA).insert( - (meta.id, json.dumps(meta.metadata)) - ) # noqa: E713 + connection.space(TARANT_TABLE_META_DATA).insert((meta.id, json.dumps(meta.metadata))) # noqa: E713 ) @@ -242,20 +250,36 @@ def get_assets(connection, assets_ids: list) -> list[Asset]: _returned_data = [] for _id in list(set(assets_ids)): res = connection.run(connection.space(TARANT_TABLE_ASSETS).select(_id, index=TARANT_TX_ID_SEARCH)) + if len(res) is 0: + continue _returned_data.append(res[0]) sorted_assets = sorted(_returned_data, key=lambda k: k[1], reverse=False) return [Asset.from_tuple(asset) for asset in sorted_assets] +@register_query(TarantoolDBConnection) +def get_assets_by_tx_id(connection, tx_id: str) -> list[Asset]: + res = connection.run(connection.space(TARANT_TABLE_ASSETS).select(tx_id, index=TARANT_TX_ID_SEARCH)) + if len(res) > 1: + return _from_tuple_list_to_asset_list(res) + + sorted_assets = sorted(res, key=lambda k: k[1], reverse=False) + return _from_tuple_list_to_asset_list(sorted_assets) + + +def _from_tuple_list_to_asset_list(_data: list) -> list[Asset]: + return [Asset.from_tuple(asset) for asset in _data] + + @register_query(TarantoolDBConnection) def get_spent(connection, fullfil_transaction_id: str, fullfil_output_index: str): _inputs = connection.run( - connection.space(TARANT_TABLE_INPUT).select([fullfil_transaction_id, str(fullfil_output_index)], - index="spent_search") + connection.space(TARANT_TABLE_INPUT).select( + [fullfil_transaction_id, str(fullfil_output_index)], index="spent_search" + ) ) - _transactions = _group_transaction_by_ids(txids=[inp[0] for inp in _inputs], connection=connection) - return _transactions + return _group_transaction_by_ids(txids=[inp[0] for inp in _inputs], connection=connection) @register_query(TarantoolDBConnection) @@ -287,7 +311,7 @@ def store_block(connection, block: dict): @register_query(TarantoolDBConnection) def get_txids_filtered( - connection, asset_ids: list[str], operation: str = None, last_tx: any = None + connection, asset_ids: list[str], operation: str = None, last_tx: any = None ): # TODO here is used 'OR' operator actions = { "CREATE": {"sets": ["CREATE", asset_ids], "index": "transaction_search"}, @@ -313,7 +337,9 @@ def get_txids_filtered( _transactions.extend(_tmp_transactions) else: _tx_ids = connection.run(connection.space(TARANT_TABLE_TRANSACTION).select(asset_ids, index=TARANT_ID_SEARCH)) - _assets_ids = connection.run(connection.space(TARANT_TABLE_ASSETS).select(asset_ids, index="only_asset_search")) + _assets_ids = connection.run( + connection.space(TARANT_TABLE_ASSETS).select(asset_ids, index="only_asset_search") + ) return tuple(set([sublist[1] for sublist in _assets_ids] + [sublist[0] for sublist in _tx_ids])) if last_tx: @@ -347,8 +373,7 @@ def get_owned_ids(connection, owner: str): if _keys is None or len(_keys) == 0: return [] _transactionids = list(set([key[1] for key in _keys])) - _transactions = _group_transaction_by_ids(txids=_transactionids, connection=connection) - return _transactions + return _group_transaction_by_ids(txids=_transactionids, connection=connection) @register_query(TarantoolDBConnection) @@ -392,20 +417,27 @@ def delete_transactions(connection, txn_ids: list): for _id in txn_ids: connection.run(connection.space(TARANT_TABLE_TRANSACTION).delete(_id), only_data=False) for _id in txn_ids: - _inputs = connection.run(connection.space(TARANT_TABLE_INPUT).select(_id, index=TARANT_ID_SEARCH), - only_data=False) - _outputs = connection.run(connection.space(TARANT_TABLE_OUTPUT).select(_id, index=TARANT_ID_SEARCH), - only_data=False) - _keys = connection.run(connection.space(TARANT_TABLE_KEYS).select(_id, index=TARANT_TX_ID_SEARCH), - only_data=False) + _inputs = connection.run( + connection.space(TARANT_TABLE_INPUT).select(_id, index=TARANT_ID_SEARCH), only_data=False + ) + _outputs = connection.run( + connection.space(TARANT_TABLE_OUTPUT).select(_id, index=TARANT_ID_SEARCH), only_data=False + ) + _keys = connection.run( + connection.space(TARANT_TABLE_KEYS).select(_id, index=TARANT_TX_ID_SEARCH), only_data=False + ) for _kID in _keys: - connection.run(connection.space(TARANT_TABLE_KEYS).delete(_kID[0], index=TARANT_ID_SEARCH), only_data=False) + connection.run( + connection.space(TARANT_TABLE_KEYS).delete(_kID[0], index=TARANT_ID_SEARCH), only_data=False + ) for _inpID in _inputs: - connection.run(connection.space(TARANT_TABLE_INPUT).delete(_inpID[5], index="delete_search"), - only_data=False) + connection.run( + connection.space(TARANT_TABLE_INPUT).delete(_inpID[5], index="delete_search"), only_data=False + ) for _outpID in _outputs: - connection.run(connection.space(TARANT_TABLE_OUTPUT).delete(_outpID[5], index="unique_search"), - only_data=False) + connection.run( + connection.space(TARANT_TABLE_OUTPUT).delete(_outpID[5], index="unique_search"), only_data=False + ) for _id in txn_ids: connection.run(connection.space(TARANT_TABLE_META_DATA).delete(_id, index=TARANT_ID_SEARCH), only_data=False) @@ -541,15 +573,14 @@ def get_election(connection, election_id: str): @register_query(TarantoolDBConnection) def get_asset_tokens_for_public_key( - connection, asset_id: str, public_key: str + connection, asset_id: str, public_key: str ): # FIXME Something can be wrong with this function ! (public_key) is not used # noqa: E501 # space = connection.space("keys") # _keys = space.select([public_key], index="keys_search") _transactions = connection.run(connection.space(TARANT_TABLE_ASSETS).select([asset_id], index="assetid_search")) # _transactions = _transactions # _keys = _keys.data - _grouped_transactions = _group_transaction_by_ids(connection=connection, txids=[_tx[1] for _tx in _transactions]) - return _grouped_transactions + return _group_transaction_by_ids(connection=connection, txids=[_tx[1] for _tx in _transactions]) @register_query(TarantoolDBConnection) diff --git a/planetmint/backend/tarantool/transaction/tools.py b/planetmint/backend/tarantool/transaction/tools.py index 19e82b7..cd4d0ac 100644 --- a/planetmint/backend/tarantool/transaction/tools.py +++ b/planetmint/backend/tarantool/transaction/tools.py @@ -1,7 +1,14 @@ from transactions.common.memoize import HDict -from planetmint.backend.tarantool.const import TARANT_TABLE_META_DATA, TARANT_TABLE_ASSETS, TARANT_TABLE_KEYS, \ - TARANT_TABLE_TRANSACTION, TARANT_TABLE_INPUT, TARANT_TABLE_OUTPUT, TARANT_TABLE_SCRIPT +from planetmint.backend.tarantool.const import ( + TARANT_TABLE_META_DATA, + TARANT_TABLE_ASSETS, + TARANT_TABLE_KEYS, + TARANT_TABLE_TRANSACTION, + TARANT_TABLE_INPUT, + TARANT_TABLE_OUTPUT, + TARANT_TABLE_SCRIPT, +) def get_items(_list): diff --git a/planetmint/fastquery.py b/planetmint/fastquery.py index 7832a5c..dae6dd8 100644 --- a/planetmint/fastquery.py +++ b/planetmint/fastquery.py @@ -18,7 +18,7 @@ class FastQuery: """Get outputs for a public key""" txs = list(query.get_owned_ids(self.connection, public_key)) return [ - TransactionLink(tx["id"], index) + TransactionLink(tx["transactions"].id, index) for tx in txs for index, output in enumerate(tx["outputs"]) if condition_details_has_owner(output["condition"]["details"], public_key) diff --git a/planetmint/lib.py b/planetmint/lib.py index bcfd977..26cbbbb 100644 --- a/planetmint/lib.py +++ b/planetmint/lib.py @@ -8,11 +8,8 @@ MongoDB. """ import logging -from collections import namedtuple -from uuid import uuid4 from planetmint.backend.connection import Connection -from hashlib import sha3_256 import json import rapidjson import requests @@ -40,6 +37,7 @@ from transactions.common.transaction import VALIDATOR_ELECTION, CHAIN_MIGRATION_ from transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT, BROADCAST_TX_ASYNC, BROADCAST_TX_SYNC from transactions.types.elections.election import Election from transactions.types.elections.validator_utils import election_id_to_public_key + from planetmint.config import Config from planetmint import backend, config_utils, fastquery from planetmint.tendermint_utils import ( @@ -226,12 +224,11 @@ class Planetmint(object): return backend.query.delete_unspent_outputs(self.connection, *unspent_outputs) def is_committed(self, transaction_id): - transaction = backend.query.get_transaction(self.connection, transaction_id) + transaction = backend.query.get_transaction_space_by_id(self.connection, transaction_id) return bool(transaction) def get_transaction(self, transaction_id): - transaction = backend.query.get_transaction(self.connection, transaction_id) - return Transaction.from_dict(transaction, False) + return backend.query.get_transaction_single(self.connection, transaction_id) def get_transactions(self, txn_ids): return backend.query.get_transactions(self.connection, txn_ids) @@ -265,7 +262,6 @@ class Planetmint(object): def get_spent(self, txid, output, current_transactions=[]): transactions = backend.query.get_spent(self.connection, txid, output) - transactions = list(transactions) if transactions else [] if len(transactions) > 1: raise core_exceptions.CriticalDoubleSpend( "`{}` was spent more than once. There is a problem" " with the chain".format(txid) @@ -281,10 +277,10 @@ class Planetmint(object): if len(transactions) + len(current_spent_transactions) > 1: 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) + tx_id = transactions[0]["transactions"].id + tx = backend.query.get_transaction_single(self.connection, tx_id) + tx.assets = backend.query.get_assets_by_tx_id(self.connection, tx_id) + transaction = tx.to_dict() elif current_spent_transactions: transaction = current_spent_transactions[0] @@ -697,7 +693,7 @@ class Planetmint(object): return recipients def show_election_status(self, transaction): - data = transaction.assets[0]["data"] + data = transaction.assets[0] if "public_key" in data.keys(): data["public_key"] = public_key_to_base64(data["public_key"]["value"]) response = "" @@ -746,23 +742,23 @@ class Planetmint(object): # validators and their voting power in the network return current_topology == voters - def count_votes(self, election_pk, transactions, getter=getattr): + def count_votes(self, election_pk, transactions): votes = 0 for txn in transactions: - if getter(txn, "operation") == Vote.OPERATION: - for output in getter(txn, "outputs"): + if txn.operation == Vote.OPERATION: + for output in txn.outputs: # NOTE: We enforce that a valid vote to election id will have only # election_pk in the output public keys, including any other public key # along with election_pk will lead to vote being not considered valid. - if len(getter(output, "public_keys")) == 1 and [election_pk] == getter(output, "public_keys"): - votes = votes + int(getter(output, "amount")) + if len(output.public_keys) == 1 and [election_pk] == output.public_keys: + votes = votes + output.amount return votes def get_commited_votes(self, transaction, election_pk=None): # TODO: move somewhere else if election_pk is None: election_pk = election_id_to_public_key(transaction.id) - txns = list(backend.query.get_asset_tokens_for_public_key(self.connection, transaction.id, election_pk)) - return self.count_votes(election_pk, txns, dict.get) + txns = backend.query.get_asset_tokens_for_public_key(self.connection, transaction.id, election_pk) + return self.count_votes(election_pk, txns) def _get_initiated_elections(self, height, txns): # TODO: move somewhere else elections = [] @@ -855,7 +851,7 @@ class Planetmint(object): votes_committed = self.get_commited_votes(transaction, election_pk) votes_current = self.count_votes(election_pk, current_votes) - total_votes = sum(output.amount for output in transaction.outputs) + total_votes = sum(int(output.amount) for output in transaction.outputs) if (votes_committed < (2 / 3) * total_votes) and (votes_committed + votes_current >= (2 / 3) * total_votes): return True @@ -913,7 +909,7 @@ class Planetmint(object): if election.operation == CHAIN_MIGRATION_ELECTION: self.migrate_abci_chain() if election.operation == VALIDATOR_ELECTION: - validator_updates = [election.assets[0]["data"]] + validator_updates = [election.assets[0].data] curr_validator_set = self.get_validators(new_height) updated_validator_set = new_validator_set(curr_validator_set, validator_updates) @@ -921,7 +917,7 @@ class Planetmint(object): # TODO change to `new_height + 2` when upgrading to Tendermint 0.24.0. self.store_validator_set(new_height + 1, updated_validator_set) - return encode_validator(election.assets[0]["data"]) + return encode_validator(election.assets[0].data) Block = namedtuple("Block", ("app_hash", "height", "transactions")) diff --git a/tests/backend/tarantool/test_queries.py b/tests/backend/tarantool/test_queries.py index 9c704e1..d6e7eac 100644 --- a/tests/backend/tarantool/test_queries.py +++ b/tests/backend/tarantool/test_queries.py @@ -49,7 +49,7 @@ def test_write_assets(db_conn): Asset("2", "2", "2"), Asset("3", "3", "3"), # Duplicated id. Should not be written to the database - Asset("1", "1", "1") + Asset("1", "1", "1"), ] # write the assets @@ -65,7 +65,7 @@ def test_write_assets(db_conn): def test_get_assets(db_conn): from planetmint.backend.tarantool import query - + assets = [ Asset("1", "1"), Asset("2", "2"), @@ -186,7 +186,7 @@ def test_get_metadata(db_conn): metadata = [ MetaData("dd86682db39e4b424df0eec1413cfad65488fd48712097c5d865ca8e8e059b64", None), - MetaData("55a2303e3bcd653e4b5bd7118d39c0e2d48ee2f18e22fbcf64e906439bdeb45d", {"key": "value"}) + MetaData("55a2303e3bcd653e4b5bd7118d39c0e2d48ee2f18e22fbcf64e906439bdeb45d", {"key": "value"}), ] # conn.db.metadata.insert_many(deepcopy(metadata), ordered=False) @@ -204,8 +204,8 @@ def test_get_owned_ids(signed_create_tx, user_pk, db_conn): query.store_transactions(connection=db_conn, signed_transactions=[signed_create_tx.to_dict()]) txns = list(query.get_owned_ids(connection=db_conn, owner=user_pk)) tx_dict = signed_create_tx.to_dict() - founded = [tx for tx in txns if tx["id"] == tx_dict["id"]] - assert founded[0] == tx_dict + founded = [tx for tx in txns if tx["transactions"].id == tx_dict["id"]] + assert founded[0]["transactions"].raw_transaction == tx_dict def test_get_spending_transactions(user_pk, user_sk, db_conn): @@ -225,7 +225,8 @@ def test_get_spending_transactions(user_pk, user_sk, db_conn): txns = list(query.get_spending_transactions(connection=db_conn, inputs=links)) # tx3 not a member because input 1 not asked for - assert txns == [tx2.to_dict(), tx4.to_dict()] + assert txns[0]["transactions"].raw_transaction == tx2.to_dict() + assert txns[1]["transactions"].raw_transaction == tx4.to_dict() def test_get_spending_transactions_multiple_inputs(db_conn): @@ -261,7 +262,7 @@ def test_get_spending_transactions_multiple_inputs(db_conn): txns = list(query.get_spending_transactions(connection=db_conn, inputs=[li])) assert len(txns) == num if len(txns): - assert [tx["id"] for tx in txns] == match + assert [tx["transactions"].id for tx in txns] == match def test_store_block(db_conn): diff --git a/tests/db/test_planetmint_api.py b/tests/db/test_planetmint_api.py index 98dcb55..2c73339 100644 --- a/tests/db/test_planetmint_api.py +++ b/tests/db/test_planetmint_api.py @@ -110,10 +110,10 @@ class TestBigchainApi(object): before = tx.to_dict() after = tx_from_db.to_dict() - assert before["assets"][0]["data"] == after["assets"][0]["data"] + assert before["assets"][0] == after["transaction"]["assets"][0] before.pop("asset", None) - after.pop("asset", None) - assert before == after + after["transaction"].pop("asset", None) + assert before == after["transaction"] class TestTransactionValidation(object): diff --git a/tests/tendermint/test_lib.py b/tests/tendermint/test_lib.py index 2f4d64e..7eef6af 100644 --- a/tests/tendermint/test_lib.py +++ b/tests/tendermint/test_lib.py @@ -67,8 +67,8 @@ def test_asset_is_separated_from_transaciton(b): tx_dict = copy.deepcopy(tx.to_dict()) b.store_bulk_transactions([tx]) - assert "asset" not in backend.query.get_transaction(b.connection, tx.id) - assert backend.query.get_asset(b.connection, tx.id)["data"] == assets[0] + assert "asset" not in backend.query.get_transaction_single(b.connection, tx.id) + assert backend.query.get_asset(b.connection, tx.id).data == assets[0] assert b.get_transaction(tx.id).to_dict() == tx_dict @@ -479,7 +479,7 @@ def test_get_spent_key_order(b, user_pk, user_sk, user2_pk, user2_sk): inputs = tx1.to_inputs() tx2 = Transfer.generate([inputs[1]], [([user2_pk], 2)], [tx1.id]).sign([user_sk]) assert b.validate_transaction(tx2) - + b.store_bulk_transactions([tx2]) tx3 = Transfer.generate([inputs[1]], [([bob.public_key], 2)], [tx1.id]).sign([user_sk])