Transaction hierarchy (#254)

* removed Transaction class from models.py, adjusted imports and function calls

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed comments

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed empty lines

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* resolved linting error

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* adjusted import path

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added missing argument to mock

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* resolved linting error

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* adjusted mock func signature

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
Lorenz Herzberger 2022-09-13 17:27:51 +02:00 committed by GitHub
parent d971709a79
commit 22ccb26d99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 65 additions and 78 deletions

View File

@ -4,7 +4,6 @@
# Code is Apache-2.0 and docs are CC-BY-4.0 # Code is Apache-2.0 and docs are CC-BY-4.0
from planetmint.transactions.common.transaction import Transaction # noqa from planetmint.transactions.common.transaction import Transaction # noqa
from planetmint import models # noqa
from planetmint.upsert_validator import ValidatorElection # noqa from planetmint.upsert_validator import ValidatorElection # noqa
from planetmint.transactions.types.elections.vote import Vote # noqa from planetmint.transactions.types.elections.vote import Vote # noqa
from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection
@ -12,8 +11,8 @@ from planetmint.lib import Planetmint
from planetmint.core import App from planetmint.core import App
Transaction.register_type(Transaction.CREATE, models.Transaction) Transaction.register_type(Transaction.CREATE, Transaction)
Transaction.register_type(Transaction.TRANSFER, models.Transaction) Transaction.register_type(Transaction.TRANSFER, Transaction)
Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection) Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection)
Transaction.register_type(ChainMigrationElection.OPERATION, ChainMigrationElection) Transaction.register_type(ChainMigrationElection.OPERATION, ChainMigrationElection)
Transaction.register_type(Vote.OPERATION, Vote) Transaction.register_type(Vote.OPERATION, Vote)

View File

@ -24,7 +24,7 @@ import requests
import planetmint import planetmint
from planetmint.config import Config from planetmint.config import Config
from planetmint import backend, config_utils, fastquery 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.exceptions import SchemaValidationError, ValidationError, DoubleSpend
from planetmint.transactions.common.transaction_mode_types import ( from planetmint.transactions.common.transaction_mode_types import (
BROADCAST_TX_COMMIT, BROADCAST_TX_COMMIT,
@ -248,7 +248,7 @@ class Planetmint(object):
transaction.update({"metadata": metadata}) transaction.update({"metadata": metadata})
transaction = Transaction.from_dict(transaction) transaction = Transaction.from_dict(transaction, False)
return transaction return transaction
@ -301,7 +301,7 @@ class Planetmint(object):
raise DoubleSpend('tx "{}" spends inputs twice'.format(txid)) raise DoubleSpend('tx "{}" spends inputs twice'.format(txid))
elif transactions: elif transactions:
transaction = backend.query.get_transactions(self.connection, [transactions[0]["id"]]) 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: elif current_spent_transactions:
transaction = current_spent_transactions[0] transaction = current_spent_transactions[0]
@ -368,7 +368,7 @@ class Planetmint(object):
# throught the code base. # throught the code base.
if isinstance(transaction, dict): if isinstance(transaction, dict):
try: try:
transaction = Transaction.from_dict(tx) transaction = Transaction.from_dict(tx, False)
except SchemaValidationError as e: except SchemaValidationError as e:
logger.warning("Invalid transaction schema: %s", e.__cause__.message) logger.warning("Invalid transaction schema: %s", e.__cause__.message)
return False return False

View File

@ -3,56 +3,6 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are 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: class FastTransaction:
"""A minimal wrapper around a transaction dictionary. This is useful for """A minimal wrapper around a transaction dictionary. This is useful for

View File

@ -34,8 +34,11 @@ from planetmint.transactions.common.exceptions import (
InvalidSignature, InvalidSignature,
AmountError, AmountError,
AssetIdMismatch, 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 .memoize import memoize_from_dict, memoize_to_dict
from .input import Input from .input import Input
from .output import Output from .output import Output
@ -81,6 +84,9 @@ class Transaction(object):
CREATE = "CREATE" CREATE = "CREATE"
TRANSFER = "TRANSFER" TRANSFER = "TRANSFER"
ALLOWED_OPERATIONS = (CREATE, TRANSFER) ALLOWED_OPERATIONS = (CREATE, TRANSFER)
ASSET = "asset"
METADATA = "metadata"
DATA = "data"
VERSION = "2.0" VERSION = "2.0"
def __init__( def __init__(
@ -153,6 +159,32 @@ class Transaction(object):
self._id = hash_id self._id = hash_id
self.tx_dict = tx_dict 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 @property
def unspent_outputs(self): def unspent_outputs(self):
"""UnspentOutput: The outputs of this transaction, in a data """UnspentOutput: The outputs of this transaction, in a data
@ -802,7 +834,11 @@ class Transaction(object):
@classmethod @classmethod
def validate_schema(cls, tx): 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=[]): def validate_transfer_inputs(self, planet, current_transactions=[]):
# store the inputs so that we can check if the asset ids match # store the inputs so that we can check if the asset ids match

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are 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.input import Input
from planetmint.transactions.common.output import Output from planetmint.transactions.common.output import Output

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are 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 planetmint.transactions.common.output import Output
from copy import deepcopy from copy import deepcopy

View File

@ -19,7 +19,7 @@ from planetmint.transactions.common.exceptions import (
) )
from planetmint.web.views.base import make_error from planetmint.web.views.base import make_error
from planetmint.web.views import parameters from planetmint.web.views import parameters
from planetmint.models import Transaction from planetmint.transactions.common.transaction import Transaction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -76,7 +76,7 @@ class TransactionListApi(Resource):
tx = request.get_json(force=True) tx = request.get_json(force=True)
try: try:
tx_obj = Transaction.from_dict(tx) tx_obj = Transaction.from_dict(tx, False)
except SchemaValidationError as e: except SchemaValidationError as e:
return make_error( return make_error(
400, 400,

View File

@ -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): 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)]) tx_create = Create.generate([alice.public_key], [([user_pk], 1)])
assert Transaction.get_asset_id(tx_create) == tx_create.id assert Transaction.get_asset_id(tx_create) == tx_create.id
def test_get_asset_id_transfer_transaction(b, signed_create_tx, user_pk): 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) tx_transfer = Transfer.generate(signed_create_tx.to_inputs(), [([user_pk], 1)], signed_create_tx.id)
asset_id = Transaction.get_asset_id(tx_transfer) 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): 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 from planetmint.transactions.common.exceptions import AssetIdMismatch
tx1 = Create.generate([alice.public_key], [([user_pk], 1)], metadata={"msg": random.random()}) tx1 = Create.generate([alice.public_key], [([user_pk], 1)], metadata={"msg": random.random()})

View File

@ -149,7 +149,7 @@ def test_zenroom_signing():
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
tx["id"] = shared_creation_txid tx["id"] = shared_creation_txid
from planetmint.models import Transaction from planetmint.transactions.common.transaction import Transaction
from planetmint.lib import Planetmint from planetmint.lib import Planetmint
from planetmint.transactions.common.exceptions import ( from planetmint.transactions.common.exceptions import (
SchemaValidationError, SchemaValidationError,
@ -158,7 +158,7 @@ def test_zenroom_signing():
try: try:
print(f"TX\n{tx}") print(f"TX\n{tx}")
tx_obj = Transaction.from_dict(tx) tx_obj = Transaction.from_dict(tx, False)
except SchemaValidationError as e: except SchemaValidationError as e:
print(e) print(e)
assert () assert ()

View File

@ -7,6 +7,7 @@ from copy import deepcopy
import pytest import pytest
import json import json
from planetmint.transactions.common.transaction import Transaction
from planetmint.transactions.types.assets.create import Create from planetmint.transactions.types.assets.create import Create
from planetmint.transactions.types.assets.transfer import Transfer 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): def test_get_txids_filtered(signed_create_tx, signed_transfer_tx, db_conn):
from planetmint.backend.tarantool import query from planetmint.backend.tarantool import query
from planetmint.models import Transaction
# create and insert two blocks, one for the create and one for the # create and insert two blocks, one for the create and one for the
# transfer transaction # transfer transaction

View File

@ -6,7 +6,7 @@
import pytest import pytest
from copy import deepcopy 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.types.assets.create import Create
from planetmint.transactions.common.crypto import generate_key_pair from planetmint.transactions.common.crypto import generate_key_pair
from planetmint.transactions.common.memoize import to_dict, from_dict from planetmint.transactions.common.memoize import to_dict, from_dict

View File

@ -141,7 +141,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa
@pytest.fixture @pytest.fixture
def _bdb(_setup_database, _configure_planetmint): def _bdb(_setup_database, _configure_planetmint):
from planetmint.transactions.common.memoize import to_dict, from_dict 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 .utils import flush_db
from planetmint.config import Config from planetmint.config import Config

View File

@ -18,7 +18,7 @@ except ImportError:
from unittest.mock import MagicMock from unittest.mock import MagicMock
from planetmint.transactions.common.exceptions import AmountError, SchemaValidationError, ThresholdTooDeep 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 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): def validate(tx):
if isinstance(tx, Transaction): if isinstance(tx, Transaction):
tx = tx.to_dict() tx = tx.to_dict()
Transaction.from_dict(tx) Transaction.from_dict(tx, False)
def validate_raises(tx, exc=SchemaValidationError): 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 # We should test that validation works when we expect it to
def test_validation_passes(signed_create_tx): 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): def test_tx_serialization_with_incorrect_hash(signed_create_tx):
from planetmint.transactions.common.transaction import Transaction
from planetmint.transactions.common.exceptions import InvalidHash from planetmint.transactions.common.exceptions import InvalidHash
tx = signed_create_tx.to_dict() 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() tx = signed_create_tx.to_dict()
del tx["id"] del tx["id"]
with pytest.raises(InvalidHash): with pytest.raises(InvalidHash):
Transaction.from_dict(tx) Transaction.from_dict(tx, False)
################################################################################ ################################################################################

View File

@ -306,12 +306,15 @@ def test_post_invalid_transaction(
exc_cls = getattr(exceptions, exc) exc_cls = getattr(exceptions, exc)
def mock_validation(self_, tx): def mock_validation(self_, tx, skip_schema_validation=True):
raise exc_cls(msg) raise exc_cls(msg)
TransactionMock = Mock(validate=mock_validation) 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({})) res = client.post(TX_ENDPOINT, data=json.dumps({}))
expected_status_code = 400 expected_status_code = 400
expected_error_message = "Invalid transaction ({}): {}".format(exc, msg) expected_error_message = "Invalid transaction ({}): {}".format(exc, msg)