From 22ccb26d99f22005236840d507c6c392dc34d94c Mon Sep 17 00:00:00 2001 From: Lorenz Herzberger <64837895+LaurentMontBlanc@users.noreply.github.com> Date: Tue, 13 Sep 2022 17:27:51 +0200 Subject: [PATCH] Transaction hierarchy (#254) * removed Transaction class from models.py, adjusted imports and function calls Signed-off-by: Lorenz Herzberger * removed comments Signed-off-by: Lorenz Herzberger * removed empty lines Signed-off-by: Lorenz Herzberger * resolved linting error Signed-off-by: Lorenz Herzberger * adjusted import path Signed-off-by: Lorenz Herzberger * added missing argument to mock Signed-off-by: Lorenz Herzberger * resolved linting error Signed-off-by: Lorenz Herzberger * adjusted mock func signature Signed-off-by: Lorenz Herzberger Signed-off-by: Lorenz Herzberger --- planetmint/__init__.py | 5 +- planetmint/lib.py | 8 +-- planetmint/models.py | 50 ------------------- planetmint/transactions/common/transaction.py | 40 ++++++++++++++- .../transactions/types/assets/create.py | 2 +- .../transactions/types/assets/transfer.py | 2 +- planetmint/web/views/transactions.py | 4 +- tests/assets/test_digital_assets.py | 6 +-- tests/assets/test_zenroom_signing.py | 4 +- tests/backend/tarantool/test_queries.py | 2 +- tests/common/test_memoize.py | 2 +- tests/conftest.py | 2 +- .../validation/test_transaction_structure.py | 9 ++-- tests/web/test_transactions.py | 7 ++- 14 files changed, 65 insertions(+), 78 deletions(-) diff --git a/planetmint/__init__.py b/planetmint/__init__.py index 4b3e8bd..ad0a994 100644 --- a/planetmint/__init__.py +++ b/planetmint/__init__.py @@ -4,7 +4,6 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 from planetmint.transactions.common.transaction import Transaction # noqa -from planetmint import models # noqa from planetmint.upsert_validator import ValidatorElection # noqa from planetmint.transactions.types.elections.vote import Vote # noqa from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection @@ -12,8 +11,8 @@ from planetmint.lib import Planetmint from planetmint.core import App -Transaction.register_type(Transaction.CREATE, models.Transaction) -Transaction.register_type(Transaction.TRANSFER, models.Transaction) +Transaction.register_type(Transaction.CREATE, Transaction) +Transaction.register_type(Transaction.TRANSFER, Transaction) Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection) Transaction.register_type(ChainMigrationElection.OPERATION, ChainMigrationElection) Transaction.register_type(Vote.OPERATION, Vote) diff --git a/planetmint/lib.py b/planetmint/lib.py index b44953a..143a5a7 100644 --- a/planetmint/lib.py +++ b/planetmint/lib.py @@ -24,7 +24,7 @@ import requests import planetmint from planetmint.config import Config from planetmint import backend, config_utils, fastquery -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.common.exceptions import SchemaValidationError, ValidationError, DoubleSpend from planetmint.transactions.common.transaction_mode_types import ( BROADCAST_TX_COMMIT, @@ -248,7 +248,7 @@ class Planetmint(object): transaction.update({"metadata": metadata}) - transaction = Transaction.from_dict(transaction) + transaction = Transaction.from_dict(transaction, False) return transaction @@ -301,7 +301,7 @@ class Planetmint(object): raise DoubleSpend('tx "{}" spends inputs twice'.format(txid)) elif transactions: transaction = backend.query.get_transactions(self.connection, [transactions[0]["id"]]) - transaction = Transaction.from_dict(transaction[0]) + transaction = Transaction.from_dict(transaction[0], False) elif current_spent_transactions: transaction = current_spent_transactions[0] @@ -368,7 +368,7 @@ class Planetmint(object): # throught the code base. if isinstance(transaction, dict): try: - transaction = Transaction.from_dict(tx) + transaction = Transaction.from_dict(tx, False) except SchemaValidationError as e: logger.warning("Invalid transaction schema: %s", e.__cause__.message) return False diff --git a/planetmint/models.py b/planetmint/models.py index 04e534c..bea5b39 100644 --- a/planetmint/models.py +++ b/planetmint/models.py @@ -3,56 +3,6 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -from planetmint.backend.schema import validate_language_key -from planetmint.transactions.common.exceptions import InvalidSignature, DuplicateTransaction -from planetmint.transactions.common.schema import validate_transaction_schema -from planetmint.transactions.common.transaction import Transaction -from planetmint.transactions.common.utils import validate_txn_obj, validate_key - - -class Transaction(Transaction): - ASSET = "asset" - METADATA = "metadata" - DATA = "data" - - def validate(self, planet, current_transactions=[]): - """Validate transaction spend - Args: - planet (Planetmint): an instantiated planetmint.Planetmint object. - Returns: - The transaction (Transaction) if the transaction is valid else it - raises an exception describing the reason why the transaction is - invalid. - Raises: - ValidationError: If the transaction is invalid - """ - input_conditions = [] - - if self.operation == Transaction.CREATE: - duplicates = any(txn for txn in current_transactions if txn.id == self.id) - if planet.is_committed(self.id) or duplicates: - raise DuplicateTransaction("transaction `{}` already exists".format(self.id)) - - if not self.inputs_valid(input_conditions): - raise InvalidSignature("Transaction signature is invalid.") - - elif self.operation == Transaction.TRANSFER: - self.validate_transfer_inputs(planet, current_transactions) - - return self - - @classmethod - def from_dict(cls, tx_body): - return super().from_dict(tx_body, False) - - @classmethod - def validate_schema(cls, tx_body): - validate_transaction_schema(tx_body) - validate_txn_obj(cls.ASSET, tx_body[cls.ASSET], cls.DATA, validate_key) - validate_txn_obj(cls.METADATA, tx_body, cls.METADATA, validate_key) - validate_language_key(tx_body[cls.ASSET], cls.DATA) - validate_language_key(tx_body, cls.METADATA) - class FastTransaction: """A minimal wrapper around a transaction dictionary. This is useful for diff --git a/planetmint/transactions/common/transaction.py b/planetmint/transactions/common/transaction.py index 9ffc93b..ce2050e 100644 --- a/planetmint/transactions/common/transaction.py +++ b/planetmint/transactions/common/transaction.py @@ -34,8 +34,11 @@ from planetmint.transactions.common.exceptions import ( InvalidSignature, AmountError, AssetIdMismatch, + DuplicateTransaction, ) -from planetmint.transactions.common.utils import serialize +from planetmint.backend.schema import validate_language_key +from planetmint.transactions.common.schema import validate_transaction_schema +from planetmint.transactions.common.utils import serialize, validate_txn_obj, validate_key from .memoize import memoize_from_dict, memoize_to_dict from .input import Input from .output import Output @@ -81,6 +84,9 @@ class Transaction(object): CREATE = "CREATE" TRANSFER = "TRANSFER" ALLOWED_OPERATIONS = (CREATE, TRANSFER) + ASSET = "asset" + METADATA = "metadata" + DATA = "data" VERSION = "2.0" def __init__( @@ -153,6 +159,32 @@ class Transaction(object): self._id = hash_id self.tx_dict = tx_dict + def validate(self, planet, current_transactions=[]): + """Validate transaction spend + Args: + planet (Planetmint): an instantiated planetmint.Planetmint object. + Returns: + The transaction (Transaction) if the transaction is valid else it + raises an exception describing the reason why the transaction is + invalid. + Raises: + ValidationError: If the transaction is invalid + """ + input_conditions = [] + + if self.operation == Transaction.CREATE: + duplicates = any(txn for txn in current_transactions if txn.id == self.id) + if planet.is_committed(self.id) or duplicates: + raise DuplicateTransaction("transaction `{}` already exists".format(self.id)) + + if not self.inputs_valid(input_conditions): + raise InvalidSignature("Transaction signature is invalid.") + + elif self.operation == Transaction.TRANSFER: + self.validate_transfer_inputs(planet, current_transactions) + + return self + @property def unspent_outputs(self): """UnspentOutput: The outputs of this transaction, in a data @@ -802,7 +834,11 @@ class Transaction(object): @classmethod def validate_schema(cls, tx): - pass + validate_transaction_schema(tx) + validate_txn_obj(cls.ASSET, tx[cls.ASSET], cls.DATA, validate_key) + validate_txn_obj(cls.METADATA, tx, cls.METADATA, validate_key) + validate_language_key(tx[cls.ASSET], cls.DATA) + validate_language_key(tx, cls.METADATA) def validate_transfer_inputs(self, planet, current_transactions=[]): # store the inputs so that we can check if the asset ids match diff --git a/planetmint/transactions/types/assets/create.py b/planetmint/transactions/types/assets/create.py index e878186..94f8ffe 100644 --- a/planetmint/transactions/types/assets/create.py +++ b/planetmint/transactions/types/assets/create.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.common.input import Input from planetmint.transactions.common.output import Output diff --git a/planetmint/transactions/types/assets/transfer.py b/planetmint/transactions/types/assets/transfer.py index 057150a..f99dc0a 100644 --- a/planetmint/transactions/types/assets/transfer.py +++ b/planetmint/transactions/types/assets/transfer.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.common.output import Output from copy import deepcopy diff --git a/planetmint/web/views/transactions.py b/planetmint/web/views/transactions.py index 4fb8482..fff024b 100644 --- a/planetmint/web/views/transactions.py +++ b/planetmint/web/views/transactions.py @@ -19,7 +19,7 @@ from planetmint.transactions.common.exceptions import ( ) from planetmint.web.views.base import make_error from planetmint.web.views import parameters -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import Transaction logger = logging.getLogger(__name__) @@ -76,7 +76,7 @@ class TransactionListApi(Resource): tx = request.get_json(force=True) try: - tx_obj = Transaction.from_dict(tx) + tx_obj = Transaction.from_dict(tx, False) except SchemaValidationError as e: return make_error( 400, diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index cce224d..92969c5 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -33,14 +33,14 @@ def test_validate_transfer_asset_id_mismatch(b, signed_create_tx, user_pk, user_ def test_get_asset_id_create_transaction(alice, user_pk): - from planetmint.models import Transaction + from planetmint.transactions.common.transaction import Transaction tx_create = Create.generate([alice.public_key], [([user_pk], 1)]) assert Transaction.get_asset_id(tx_create) == tx_create.id def test_get_asset_id_transfer_transaction(b, signed_create_tx, user_pk): - from planetmint.models import Transaction + from planetmint.transactions.common.transaction import Transaction tx_transfer = Transfer.generate(signed_create_tx.to_inputs(), [([user_pk], 1)], signed_create_tx.id) asset_id = Transaction.get_asset_id(tx_transfer) @@ -48,7 +48,7 @@ def test_get_asset_id_transfer_transaction(b, signed_create_tx, user_pk): def test_asset_id_mismatch(alice, user_pk): - from planetmint.models import Transaction + from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.common.exceptions import AssetIdMismatch tx1 = Create.generate([alice.public_key], [([user_pk], 1)], metadata={"msg": random.random()}) diff --git a/tests/assets/test_zenroom_signing.py b/tests/assets/test_zenroom_signing.py index 968e3f3..b52f7c5 100644 --- a/tests/assets/test_zenroom_signing.py +++ b/tests/assets/test_zenroom_signing.py @@ -149,7 +149,7 @@ def test_zenroom_signing(): shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() tx["id"] = shared_creation_txid - from planetmint.models import Transaction + from planetmint.transactions.common.transaction import Transaction from planetmint.lib import Planetmint from planetmint.transactions.common.exceptions import ( SchemaValidationError, @@ -158,7 +158,7 @@ def test_zenroom_signing(): try: print(f"TX\n{tx}") - tx_obj = Transaction.from_dict(tx) + tx_obj = Transaction.from_dict(tx, False) except SchemaValidationError as e: print(e) assert () diff --git a/tests/backend/tarantool/test_queries.py b/tests/backend/tarantool/test_queries.py index f613640..104fa94 100644 --- a/tests/backend/tarantool/test_queries.py +++ b/tests/backend/tarantool/test_queries.py @@ -7,6 +7,7 @@ from copy import deepcopy import pytest import json +from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.types.assets.create import Create from planetmint.transactions.types.assets.transfer import Transfer @@ -15,7 +16,6 @@ pytestmark = pytest.mark.bdb def test_get_txids_filtered(signed_create_tx, signed_transfer_tx, db_conn): from planetmint.backend.tarantool import query - from planetmint.models import Transaction # create and insert two blocks, one for the create and one for the # transfer transaction diff --git a/tests/common/test_memoize.py b/tests/common/test_memoize.py index 30cd414..7c25943 100644 --- a/tests/common/test_memoize.py +++ b/tests/common/test_memoize.py @@ -6,7 +6,7 @@ import pytest from copy import deepcopy -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.types.assets.create import Create from planetmint.transactions.common.crypto import generate_key_pair from planetmint.transactions.common.memoize import to_dict, from_dict diff --git a/tests/conftest.py b/tests/conftest.py index 365daa6..b73b505 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -141,7 +141,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa @pytest.fixture def _bdb(_setup_database, _configure_planetmint): from planetmint.transactions.common.memoize import to_dict, from_dict - from planetmint.models import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import flush_db from planetmint.config import Config diff --git a/tests/validation/test_transaction_structure.py b/tests/validation/test_transaction_structure.py index 41ed2fd..bbb5f81 100644 --- a/tests/validation/test_transaction_structure.py +++ b/tests/validation/test_transaction_structure.py @@ -18,7 +18,7 @@ except ImportError: from unittest.mock import MagicMock from planetmint.transactions.common.exceptions import AmountError, SchemaValidationError, ThresholdTooDeep -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.common.utils import _fulfillment_to_details, _fulfillment_from_details ################################################################################ @@ -28,7 +28,7 @@ from planetmint.transactions.common.utils import _fulfillment_to_details, _fulfi def validate(tx): if isinstance(tx, Transaction): tx = tx.to_dict() - Transaction.from_dict(tx) + Transaction.from_dict(tx, False) def validate_raises(tx, exc=SchemaValidationError): @@ -38,7 +38,7 @@ def validate_raises(tx, exc=SchemaValidationError): # We should test that validation works when we expect it to def test_validation_passes(signed_create_tx): - Transaction.from_dict(signed_create_tx.to_dict()) + Transaction.from_dict(signed_create_tx.to_dict(), False) ################################################################################ @@ -53,7 +53,6 @@ def test_tx_serialization_hash_function(signed_create_tx): def test_tx_serialization_with_incorrect_hash(signed_create_tx): - from planetmint.transactions.common.transaction import Transaction from planetmint.transactions.common.exceptions import InvalidHash tx = signed_create_tx.to_dict() @@ -68,7 +67,7 @@ def test_tx_serialization_with_no_hash(signed_create_tx): tx = signed_create_tx.to_dict() del tx["id"] with pytest.raises(InvalidHash): - Transaction.from_dict(tx) + Transaction.from_dict(tx, False) ################################################################################ diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index e85688a..42baabe 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -306,12 +306,15 @@ def test_post_invalid_transaction( exc_cls = getattr(exceptions, exc) - def mock_validation(self_, tx): + def mock_validation(self_, tx, skip_schema_validation=True): raise exc_cls(msg) TransactionMock = Mock(validate=mock_validation) - monkeypatch.setattr("planetmint.models.Transaction.from_dict", lambda tx: TransactionMock) + monkeypatch.setattr( + "planetmint.transactions.common.transaction.Transaction.from_dict", + lambda tx, skip_schema_validation: TransactionMock, + ) res = client.post(TX_ENDPOINT, data=json.dumps({})) expected_status_code = 400 expected_error_message = "Invalid transaction ({}): {}".format(exc, msg)