diff --git a/planetmint/model/models.py b/planetmint/model/models.py index 7ef9b45..babb969 100644 --- a/planetmint/model/models.py +++ b/planetmint/model/models.py @@ -12,15 +12,14 @@ from planetmint.backend.connection import Connection from planetmint.backend.tarantool.const import TARANT_TABLE_TRANSACTION, TARANT_TABLE_GOVERNANCE from planetmint.model.fastquery import FastQuery from planetmint.abci.tendermint_utils import key_from_base64 -from planetmint.abci.tendermint_utils import merkleroot -from hashlib import sha3_256 from planetmint.backend.models.block import Block from planetmint.backend.models.output import Output from planetmint.backend.models.asset import Asset -from planetmint.backend.models.input import Input from planetmint.backend.models.metadata import MetaData from planetmint.backend.models.dbtransaction import DbTransaction + + class Models: def __init__(self, database_connection = None): config_utils.autoconfigure() @@ -142,77 +141,6 @@ class Models: return validators - - def tests_update_utxoset(self, transaction): - self.updated__ = """Update the UTXO set given ``transaction``. That is, remove - the outputs that the given ``transaction`` spends, and add the - outputs that the given ``transaction`` creates. - - Args: - transaction (:obj:`~planetmint.models.Transaction`): A new - transaction incoming into the system for which the UTXOF - set needs to be updated. - """ - spent_outputs = [spent_output for spent_output in transaction.spent_outputs] - if spent_outputs: - self.tests_delete_unspent_outputs(*spent_outputs) - self.tests_store_unspent_outputs(*[utxo._asdict() for utxo in transaction.unspent_outputs]) - - def tests_store_unspent_outputs(self, *unspent_outputs): - """Store the given ``unspent_outputs`` (utxos). - - Args: - *unspent_outputs (:obj:`tuple` of :obj:`dict`): Variable - length tuple or list of unspent outputs. - """ - if unspent_outputs: - return backend.query.store_unspent_outputs(self.connection, *unspent_outputs) - - def tests_get_utxoset_merkle_root(self): - """Returns the merkle root of the utxoset. This implies that - the utxoset is first put into a merkle tree. - - For now, the merkle tree and its root will be computed each - time. This obviously is not efficient and a better approach - that limits the repetition of the same computation when - unnecesary should be sought. For instance, future optimizations - could simply re-compute the branches of the tree that were - affected by a change. - - The transaction hash (id) and output index should be sufficient - to uniquely identify a utxo, and consequently only that - information from a utxo record is needed to compute the merkle - root. Hence, each node of the merkle tree should contain the - tuple (txid, output_index). - - .. important:: The leaves of the tree will need to be sorted in - some kind of lexicographical order. - - Returns: - str: Merkle root in hexadecimal form. - """ - utxoset = backend.query.get_unspent_outputs(self.connection) - # TODO Once ready, use the already pre-computed utxo_hash field. - # See common/transactions.py for details. - hashes = [ - sha3_256("{}{}".format(utxo["transaction_id"], utxo["output_index"]).encode()).digest() for utxo in utxoset - ] - # TODO Notice the sorted call! - return merkleroot(sorted(hashes)) - - - def tests_delete_unspent_outputs(self, *unspent_outputs): - """Deletes the given ``unspent_outputs`` (utxos). - - Args: - *unspent_outputs (:obj:`tuple` of :obj:`dict`): Variable - length tuple or list of unspent outputs. - """ - if unspent_outputs: - return backend.query.delete_unspent_outputs(self.connection, *unspent_outputs) - - - def get_spent(self, txid, output, current_transactions=[]) -> DbTransaction: transactions = backend.query.get_spent(self.connection, txid, output) diff --git a/tests/tendermint/test_lib.py b/tests/tendermint/test_lib.py index 68544da..32101be 100644 --- a/tests/tendermint/test_lib.py +++ b/tests/tendermint/test_lib.py @@ -23,6 +23,9 @@ from uuid import uuid4 from planetmint.abci.rpc import ABCI_RPC from planetmint.abci.rpc import MODE_COMMIT, MODE_LIST +from tests.utils import delete_unspent_outputs, get_utxoset_merkle_root, store_unspent_outputs, \ + update_utxoset + @pytest.mark.bdb def test_asset_is_separated_from_transaciton(b): @@ -149,13 +152,13 @@ def test_post_transaction_invalid_mode(b, test_abci_rpc): @pytest.mark.bdb def test_update_utxoset(b, signed_create_tx, signed_transfer_tx, db_conn): - b.models.tests_update_utxoset(signed_create_tx) + update_utxoset(b.models.connection, signed_create_tx) utxoset = db_conn.get_space("utxos") assert utxoset.select().rowcount == 1 utxo = utxoset.select().data assert utxo[0][1] == signed_create_tx.id assert utxo[0][2] == 0 - b.models.tests_update_utxoset(signed_transfer_tx) + update_utxoset(b.models.connection, signed_transfer_tx) assert utxoset.select().rowcount == 1 utxo = utxoset.select().data assert utxo[0][1] == signed_transfer_tx.id @@ -184,7 +187,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx, signed_transfer_tx) def test_delete_zero_unspent_outputs(b, utxoset): unspent_outputs, utxo_collection = utxoset num_rows_before_operation = utxo_collection.select().rowcount - delete_res = b.models.tests_delete_unspent_outputs() # noqa: F841 + delete_res = delete_unspent_outputs(b.models.connection) # noqa: F841 num_rows_after_operation = utxo_collection.select().rowcount # assert delete_res is None assert num_rows_before_operation == num_rows_after_operation @@ -197,7 +200,7 @@ def test_delete_one_unspent_outputs(b, dummy_unspent_outputs): res = utxo_space.insert((uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo)) assert res - b.models.tests_delete_unspent_outputs(dummy_unspent_outputs[0]) + delete_unspent_outputs(b.models.connection, dummy_unspent_outputs[0]) res1 = utxo_space.select(["a", 1], index="utxo_by_transaction_id_and_output_index").data res2 = utxo_space.select(["b", 0], index="utxo_by_transaction_id_and_output_index").data assert len(res1) + len(res2) == 2 @@ -212,7 +215,7 @@ def test_delete_many_unspent_outputs(b, dummy_unspent_outputs): res = utxo_space.insert((uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo)) assert res - b.models.tests_delete_unspent_outputs(*dummy_unspent_outputs[::2]) + delete_unspent_outputs(b.models.connection, *dummy_unspent_outputs[::2]) res1 = utxo_space.select(["a", 0], index="utxo_by_transaction_id_and_output_index").data res2 = utxo_space.select(["b", 0], index="utxo_by_transaction_id_and_output_index").data assert len(res1) + len(res2) == 0 @@ -224,7 +227,7 @@ def test_delete_many_unspent_outputs(b, dummy_unspent_outputs): def test_store_zero_unspent_output(b): utxos = b.models.connection.get_space("utxos") num_rows_before_operation = utxos.select().rowcount - res = b.models.tests_store_unspent_outputs() + res = store_unspent_outputs(b.models.connection) num_rows_after_operation = utxos.select().rowcount assert res is None assert num_rows_before_operation == num_rows_after_operation @@ -234,7 +237,7 @@ def test_store_zero_unspent_output(b): def test_store_one_unspent_output(b, unspent_output_1, utxo_collection): from planetmint.backend.tarantool.connection import TarantoolDBConnection - res = b.models.tests_store_unspent_outputs(unspent_output_1) + res = store_unspent_outputs(b.models.connection, unspent_output_1) if not isinstance(b.models.connection, TarantoolDBConnection): assert res.acknowledged assert len(list(res)) == 1 @@ -258,14 +261,14 @@ def test_store_one_unspent_output(b, unspent_output_1, utxo_collection): @pytest.mark.bdb def test_store_many_unspent_outputs(b, unspent_outputs): - b.models.tests_store_unspent_outputs(*unspent_outputs) + store_unspent_outputs(b.models.connection, *unspent_outputs) utxo_space = b.models.connection.get_space("utxos") res = utxo_space.select([unspent_outputs[0]["transaction_id"]], index="utxos_by_transaction_id") assert len(res.data) == 3 def test_get_utxoset_merkle_root_when_no_utxo(b): - assert b.models.tests_get_utxoset_merkle_root() == sha3_256(b"").hexdigest() + assert get_utxoset_merkle_root(b.models.connection) == sha3_256(b"").hexdigest() @pytest.mark.bdb @@ -276,7 +279,7 @@ def test_get_utxoset_merkle_root(b, dummy_unspent_outputs): assert res expected_merkle_root = "86d311c03115bf4d287f8449ca5828505432d69b82762d47077b1c00fe426eac" - merkle_root = b.models.tests_get_utxoset_merkle_root() + merkle_root = get_utxoset_merkle_root(b.models.connection) assert merkle_root == expected_merkle_root diff --git a/tests/utils.py b/tests/utils.py index 1c20600..0089392 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,7 @@ # 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 hashlib import sha3_256 import base58 import base64 @@ -9,6 +10,7 @@ import random from functools import singledispatch +from planetmint import backend from planetmint.abci.rpc import ABCI_RPC from planetmint.backend.localmongodb.connection import LocalMongoDBConnection from planetmint.backend.tarantool.connection import TarantoolDBConnection @@ -18,7 +20,7 @@ from transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT from transactions.types.assets.create import Create from transactions.types.elections.vote import Vote from transactions.types.elections.validator_utils import election_id_to_public_key -from planetmint.abci.tendermint_utils import key_to_base64 +from planetmint.abci.tendermint_utils import key_to_base64, merkleroot from planetmint.abci.rpc import MODE_COMMIT, MODE_LIST @@ -123,3 +125,75 @@ def generate_election(b, cls, public_key, private_key, asset_data, voter_keys): v.sign([key]) return election, votes + + +def delete_unspent_outputs(connection, *unspent_outputs): + """Deletes the given ``unspent_outputs`` (utxos). + + Args: + *unspent_outputs (:obj:`tuple` of :obj:`dict`): Variable + length tuple or list of unspent outputs. + """ + if unspent_outputs: + return backend.query.delete_unspent_outputs(connection, *unspent_outputs) + + +def get_utxoset_merkle_root(connection): + """Returns the merkle root of the utxoset. This implies that + the utxoset is first put into a merkle tree. + + For now, the merkle tree and its root will be computed each + time. This obviously is not efficient and a better approach + that limits the repetition of the same computation when + unnecesary should be sought. For instance, future optimizations + could simply re-compute the branches of the tree that were + affected by a change. + + The transaction hash (id) and output index should be sufficient + to uniquely identify a utxo, and consequently only that + information from a utxo record is needed to compute the merkle + root. Hence, each node of the merkle tree should contain the + tuple (txid, output_index). + + .. important:: The leaves of the tree will need to be sorted in + some kind of lexicographical order. + + Returns: + str: Merkle root in hexadecimal form. + """ + utxoset = backend.query.get_unspent_outputs(connection) + # TODO Once ready, use the already pre-computed utxo_hash field. + # See common/transactions.py for details. + hashes = [ + sha3_256("{}{}".format(utxo["transaction_id"], utxo["output_index"]).encode()).digest() for utxo in utxoset + ] + # TODO Notice the sorted call! + return merkleroot(sorted(hashes)) + + +def store_unspent_outputs(connection, *unspent_outputs): + """Store the given ``unspent_outputs`` (utxos). + + Args: + *unspent_outputs (:obj:`tuple` of :obj:`dict`): Variable + length tuple or list of unspent outputs. + """ + if unspent_outputs: + return backend.query.store_unspent_outputs(connection, *unspent_outputs) + + +def update_utxoset(connection, transaction): + """ + Update the UTXO set given ``transaction``. That is, remove + the outputs that the given ``transaction`` spends, and add the + outputs that the given ``transaction`` creates. + + Args: + transaction (:obj:`~planetmint.models.Transaction`): A new + transaction incoming into the system for which the UTXOF + set needs to be updated. + """ + spent_outputs = [spent_output for spent_output in transaction.spent_outputs] + if spent_outputs: + delete_unspent_outputs(connection, *spent_outputs) + store_unspent_outputs(connection, *[utxo._asdict() for utxo in transaction.unspent_outputs]) \ No newline at end of file