diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index 9bcb5e1f..c87b9864 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -3,13 +3,13 @@ from functools import reduce from uuid import uuid4 from cryptoconditions import (Fulfillment as CCFulfillment, - ThresholdSha256Fulfillment, Ed25519Fulfillment, - PreimageSha256Fulfillment) + ThresholdSha256Fulfillment, Ed25519Fulfillment) from cryptoconditions.exceptions import ParsingError from bigchaindb.common.crypto import SigningKey, hash_data from bigchaindb.common.exceptions import (KeypairMismatchException, - InvalidHash, InvalidSignature) + InvalidHash, InvalidSignature, + AmountError, AssetIdMismatch) from bigchaindb.common.util import serialize, gen_timestamp @@ -96,6 +96,14 @@ class Fulfillment(object): ffill['fid'] = fid return ffill + @classmethod + def generate(cls, owners_before): + # TODO: write docstring + # The amount here does not really matter. It is only use on the + # condition data model but here we only care about the fulfillment + condition = Condition.generate(owners_before, 1) + return cls(condition.fulfillment, condition.owners_after) + @classmethod def from_dict(cls, ffill): """Transforms a Python dictionary to a Fulfillment object. @@ -265,7 +273,7 @@ class Condition(object): return cond @classmethod - def generate(cls, owners_after): + def generate(cls, owners_after, amount): """Generates a Condition from a specifically formed tuple or list. Note: @@ -275,34 +283,24 @@ class Condition(object): [(address|condition)*, [(address|condition)*, ...], ...] - If however, the thresholds of individual threshold conditions - to be created have to be set specifically, a tuple of the - following structure is necessary: - - ([(address|condition)*, - ([(address|condition)*, ...], subthreshold), - ...], threshold) - Args: - owners_after (:obj:`list` of :obj:`str`|tuple): The users that - should be able to fulfill the Condition that is being - created. + owners_after (:obj:`list` of :obj:`str`): The public key of + the users that should be able to fulfill the Condition + that is being created. + amount (:obj:`int`): The amount locked by the condition. Returns: A Condition that can be used in a Transaction. - Returns: + Raises: TypeError: If `owners_after` is not an instance of `list`. - TypeError: If `owners_after` is an empty list. + ValueError: If `owners_after` is an empty list. """ - # TODO: We probably want to remove the tuple logic for weights here - # again: - # github.com/bigchaindb/bigchaindb/issues/730#issuecomment-255144756 - if isinstance(owners_after, tuple): - owners_after, threshold = owners_after - else: - threshold = len(owners_after) - + threshold = len(owners_after) + if not isinstance(amount, int): + raise TypeError('`amount` must be a int') + if amount < 1: + raise AmountError('`amount` needs to be greater than zero') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be an instance of list') if len(owners_after) == 0: @@ -313,12 +311,12 @@ class Condition(object): ffill = Ed25519Fulfillment(public_key=owners_after[0]) except TypeError: ffill = owners_after[0] - return cls(ffill, owners_after) + return cls(ffill, owners_after, amount=amount) else: initial_cond = ThresholdSha256Fulfillment(threshold=threshold) threshold_cond = reduce(cls._gen_condition, owners_after, initial_cond) - return cls(threshold_cond, owners_after) + return cls(threshold_cond, owners_after, amount=amount) @classmethod def _gen_condition(cls, initial, current): @@ -338,14 +336,11 @@ class Condition(object): Returns: :class:`cryptoconditions.ThresholdSha256Fulfillment`: """ - if isinstance(current, tuple): - owners_after, threshold = current - else: - owners_after = current - try: - threshold = len(owners_after) - except TypeError: - threshold = None + owners_after = current + try: + threshold = len(owners_after) + except TypeError: + threshold = None if isinstance(owners_after, list) and len(owners_after) > 1: ffill = ThresholdSha256Fulfillment(threshold=threshold) @@ -420,7 +415,7 @@ class Asset(object): self.updatable = updatable self.refillable = refillable - self._validate_asset() + self.validate_asset() def __eq__(self, other): try: @@ -463,7 +458,38 @@ class Asset(object): """Generates a unqiue uuid for an Asset""" return str(uuid4()) - def _validate_asset(self): + @staticmethod + def get_asset_id(transactions): + """Get the asset id from a list of transaction ids. + + This is useful when we want to check if the multiple inputs of a + transaction are related to the same asset id. + + Args: + transactions (:obj:`list` of :class:`~bigchaindb.common. + transaction.Transaction`): list of transaction usually inputs + that should have a matching asset_id + + Returns: + str: uuid of the asset. + + Raises: + AssetIdMismatch: If the inputs are related to different assets. + """ + + if not isinstance(transactions, list): + transactions = [transactions] + + # create a set of asset_ids + asset_ids = {tx.asset.data_id for tx in transactions} + + # check that all the transasctions have the same asset_id + if len(asset_ids) > 1: + raise AssetIdMismatch(('All inputs of all transactions passed' + ' need to have the same asset id')) + return asset_ids.pop() + + def validate_asset(self, amount=None): """Validates the asset""" if self.data is not None and not isinstance(self.data, dict): raise TypeError('`data` must be a dict instance or None') @@ -474,6 +500,77 @@ class Asset(object): if not isinstance(self.updatable, bool): raise TypeError('`updatable` must be a boolean') + if self.refillable: + raise NotImplementedError('Refillable assets are not yet' + ' implemented') + if self.updatable: + raise NotImplementedError('Updatable assets are not yet' + ' implemented') + + # If the amount is supplied we can perform extra validations to + # the asset + if amount is not None: + if not isinstance(amount, int): + raise TypeError('`amount` must be an int') + + if self.divisible is False and amount != 1: + raise AmountError('non divisible assets always have' + ' amount equal to one') + + # Since refillable assets are not yet implemented this should + # raise and exception + if self.divisible is True and amount < 2: + raise AmountError('divisible assets must have an amount' + ' greater than one') + + +class AssetLink(Asset): + """An object for unidirectional linking to a Asset. + """ + + def __init__(self, data_id=None): + """Used to point to a specific Asset. + + Args: + data_id (str): A Asset to link to. + """ + self.data_id = data_id + + def __bool__(self): + return self.data_id is not None + + def __eq__(self, other): + return isinstance(other, AssetLink) and \ + self.to_dict() == self.to_dict() + + @classmethod + def from_dict(cls, link): + """Transforms a Python dictionary to a AssetLink object. + + Args: + link (dict): The link to be transformed. + + Returns: + :class:`~bigchaindb.common.transaction.AssetLink` + """ + try: + return cls(link['id']) + except TypeError: + return cls() + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + (dict|None): The link as an alternative serialization format. + """ + if self.data_id is None: + return None + else: + return { + 'id': self.data_id + } + class Metadata(object): """Metadata is used to store a dictionary and its hash in a Transaction.""" @@ -612,9 +709,17 @@ class Transaction(object): self.fulfillments = fulfillments if fulfillments else [] self.metadata = metadata + # validate asset + # we know that each transaction relates to a single asset + # we can sum the amount of all the conditions + # for transactions other then CREATE we only have an id so there is + # nothing we can validate + if self.operation == self.CREATE: + amount = sum([condition.amount for condition in self.conditions]) + self.asset.validate_asset(amount=amount) + @classmethod - def create(cls, owners_before, owners_after, metadata=None, asset=None, - secret=None, time_expire=None): + def create(cls, owners_before, owners_after, metadata=None, asset=None): """A simple way to generate a `CREATE` transaction. Note: @@ -622,7 +727,6 @@ class Transaction(object): use cases: - Ed25519 - ThresholdSha256 - - PreimageSha256. Additionally, it provides support for the following BigchainDB use cases: @@ -637,10 +741,6 @@ class Transaction(object): Transaction. asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset to be created in this Transaction. - secret (binarystr, optional): A secret string to create a hash- - lock Condition. - time_expire (int, optional): The UNIX time a Transaction is - valid. Returns: :class:`~bigchaindb.common.transaction.Transaction` @@ -649,54 +749,28 @@ class Transaction(object): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') + if len(owners_before) == 0: + raise ValueError('`owners_before` list cannot be empty') + if len(owners_after) == 0: + raise ValueError('`owners_after` list cannot be empty') metadata = Metadata(metadata) - if len(owners_before) == len(owners_after) and len(owners_after) == 1: - # NOTE: Standard case, one owner before, one after. - # NOTE: For this case its sufficient to use the same - # fulfillment for the fulfillment and condition. - ffill = Ed25519Fulfillment(public_key=owners_before[0]) - ffill_tx = Fulfillment(ffill, owners_before) - cond_tx = Condition.generate(owners_after) - return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata) + fulfillments = [] + conditions = [] - elif len(owners_before) == len(owners_after) and len(owners_after) > 1: - raise NotImplementedError('Multiple inputs and outputs not' - 'available for CREATE') - # NOTE: Multiple inputs and outputs case. Currently not supported. - ffills = [Fulfillment(Ed25519Fulfillment(public_key=owner_before), - [owner_before]) - for owner_before in owners_before] - conds = [Condition.generate(owners) for owners in owners_after] - return cls(cls.CREATE, asset, ffills, conds, metadata) + # generate_conditions + for owner_after in owners_after: + if not isinstance(owner_after, tuple) or len(owner_after) != 2: + raise ValueError(('Each `owner_after` in the list must be a' + ' tuple of `([],' + ' )`')) + pub_keys, amount = owner_after + conditions.append(Condition.generate(pub_keys, amount)) - elif len(owners_before) == 1 and len(owners_after) > 1: - # NOTE: Multiple owners case - cond_tx = Condition.generate(owners_after) - ffill = Ed25519Fulfillment(public_key=owners_before[0]) - ffill_tx = Fulfillment(ffill, owners_before) - return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata) + # generate fulfillments + fulfillments.append(Fulfillment.generate(owners_before)) - elif (len(owners_before) == 1 and len(owners_after) == 0 and - secret is not None): - # NOTE: Hashlock condition case - hashlock = PreimageSha256Fulfillment(preimage=secret) - cond_tx = Condition(hashlock.condition_uri) - ffill = Ed25519Fulfillment(public_key=owners_before[0]) - ffill_tx = Fulfillment(ffill, owners_before) - return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata) - - elif (len(owners_before) > 0 and len(owners_after) == 0 and - time_expire is not None): - raise NotImplementedError('Timeout conditions will be implemented ' - 'later') - - elif (len(owners_before) > 0 and len(owners_after) == 0 and - secret is None): - raise ValueError('Define a secret to create a hashlock condition') - - else: - raise ValueError("These are not the cases you're looking for ;)") + return cls(cls.CREATE, asset, fulfillments, conditions, metadata) @classmethod def transfer(cls, inputs, owners_after, asset, metadata=None): @@ -743,17 +817,17 @@ class Transaction(object): raise ValueError('`inputs` must contain at least one item') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') + if len(owners_after) == 0: + raise ValueError('`owners_after` list cannot be empty') - # NOTE: See doc strings `Note` for description. - if len(inputs) == len(owners_after): - if len(owners_after) == 1: - conditions = [Condition.generate(owners_after)] - elif len(owners_after) > 1: - conditions = [Condition.generate(owners) for owners - in owners_after] - else: - raise ValueError("`inputs` and `owners_after`'s count must be the " - "same") + conditions = [] + for owner_after in owners_after: + if not isinstance(owner_after, tuple) or len(owner_after) != 2: + raise ValueError(('Each `owner_after` in the list must be a' + ' tuple of `([],' + ' )`')) + pub_keys, amount = owner_after + conditions.append(Condition.generate(pub_keys, amount)) metadata = Metadata(metadata) inputs = deepcopy(inputs) @@ -862,13 +936,12 @@ class Transaction(object): key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} - zippedIO = enumerate(zip(self.fulfillments, self.conditions)) - for index, (fulfillment, condition) in zippedIO: + for index, fulfillment in enumerate(self.fulfillments): # NOTE: We clone the current transaction but only add the condition # and fulfillment we're currently working on plus all # previously signed ones. tx_partial = Transaction(self.operation, self.asset, [fulfillment], - [condition], self.metadata, + self.conditions, self.metadata, self.timestamp, self.version) tx_partial_dict = tx_partial.to_dict() @@ -1025,14 +1098,13 @@ class Transaction(object): """ input_condition_uris_count = len(input_condition_uris) fulfillments_count = len(self.fulfillments) - conditions_count = len(self.conditions) def gen_tx(fulfillment, condition, input_condition_uri=None): """Splits multiple IO Transactions into partial single IO Transactions. """ tx = Transaction(self.operation, self.asset, [fulfillment], - [condition], self.metadata, self.timestamp, + self.conditions, self.metadata, self.timestamp, self.version) tx_dict = tx.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) @@ -1043,14 +1115,13 @@ class Transaction(object): tx_serialized, input_condition_uri) - if not fulfillments_count == conditions_count == \ - input_condition_uris_count: - raise ValueError('Fulfillments, conditions and ' + if not fulfillments_count == input_condition_uris_count: + raise ValueError('Fulfillments and ' 'input_condition_uris must have the same count') - else: - partial_transactions = map(gen_tx, self.fulfillments, - self.conditions, input_condition_uris) - return all(partial_transactions) + + partial_transactions = map(gen_tx, self.fulfillments, + self.conditions, input_condition_uris) + return all(partial_transactions) @staticmethod def _fulfillment_valid(fulfillment, operation, tx_serialized, @@ -1208,7 +1279,10 @@ class Transaction(object): conditions = [Condition.from_dict(condition) for condition in tx['conditions']] metadata = Metadata.from_dict(tx['metadata']) - asset = Asset.from_dict(tx['asset']) + if tx['operation'] in [cls.CREATE, cls.GENESIS]: + asset = Asset.from_dict(tx['asset']) + else: + asset = AssetLink.from_dict(tx['asset']) return cls(tx['operation'], asset, fulfillments, conditions, metadata, tx['timestamp'], tx_body['version']) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 7711514a..45e9be80 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -6,7 +6,7 @@ from time import time from itertools import compress from bigchaindb.common import crypto, exceptions from bigchaindb.common.util import gen_timestamp, serialize -from bigchaindb.common.transaction import TransactionLink +from bigchaindb.common.transaction import TransactionLink, Asset import bigchaindb @@ -182,10 +182,11 @@ class Bigchain(object): try: return self.validate_transaction(transaction) - except (ValueError, exceptions.OperationError, exceptions.TransactionDoesNotExist, + except (ValueError, exceptions.OperationError, + exceptions.TransactionDoesNotExist, exceptions.TransactionOwnerError, exceptions.DoubleSpend, exceptions.InvalidHash, exceptions.InvalidSignature, - exceptions.FulfillmentNotInValidBlock): + exceptions.FulfillmentNotInValidBlock, exceptions.AmountError): return False def get_block(self, block_id, include_status=False): @@ -365,6 +366,21 @@ class Bigchain(object): cursor = self.backend.get_transactions_by_asset_id(asset_id) return [Transaction.from_dict(tx) for tx in cursor] + def get_asset_by_id(self, asset_id): + """Returns the asset associated with an asset_id. + + Args: + asset_id (str): The asset id. + + Returns: + :class:`~bigchaindb.common.transaction.Asset` if the asset + exists else None. + """ + cursor = self.backend.get_asset_by_id(asset_id) + cursor = list(cursor) + if cursor: + return Asset.from_dict(cursor[0]['transaction']['asset']) + def get_spent(self, txid, cid): """Check if a `txid` was already used as an input. @@ -533,7 +549,7 @@ class Bigchain(object): """Prepare a genesis block.""" metadata = {'message': 'Hello World from the BigchainDB'} - transaction = Transaction.create([self.me], [self.me], + transaction = Transaction.create([self.me], [([self.me], 1)], metadata=metadata) # NOTE: The transaction model doesn't expose an API to generate a diff --git a/bigchaindb/db/backends/rethinkdb.py b/bigchaindb/db/backends/rethinkdb.py index c9a171e9..b9355b41 100644 --- a/bigchaindb/db/backends/rethinkdb.py +++ b/bigchaindb/db/backends/rethinkdb.py @@ -178,7 +178,27 @@ class RethinkDBBackend: r.table('bigchain', read_mode=self.read_mode) .get_all(asset_id, index='asset_id') .concat_map(lambda block: block['block']['transactions']) - .filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id)) + .filter(lambda transaction: + transaction['transaction']['asset']['id'] == asset_id)) + + def get_asset_by_id(self, asset_id): + """Returns the asset associated with an asset_id. + + Args: + asset_id (str): The asset id. + + Returns: + Returns a rethinkdb cursor. + """ + return self.connection.run( + r.table('bigchain', read_mode=self.read_mode) + .get_all(asset_id, index='asset_id') + .concat_map(lambda block: block['block']['transactions']) + .filter(lambda transaction: + transaction['transaction']['asset']['id'] == asset_id) + .filter(lambda transaction: + transaction['transaction']['operation'] == 'CREATE') + .pluck({'transaction': 'asset'})) def get_spent(self, transaction_id, condition_id): """Check if a `txid` was already used as an input. diff --git a/bigchaindb/models.py b/bigchaindb/models.py index 57d64272..efee0502 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -3,41 +3,11 @@ from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature, OperationError, DoubleSpend, TransactionDoesNotExist, FulfillmentNotInValidBlock, - AssetIdMismatch) + AssetIdMismatch, AmountError) from bigchaindb.common.transaction import Transaction, Asset from bigchaindb.common.util import gen_timestamp, serialize -class Asset(Asset): - @staticmethod - def get_asset_id(transactions): - """Get the asset id from a list of transaction ids. - - This is useful when we want to check if the multiple inputs of a transaction - are related to the same asset id. - - Args: - transactions (list): list of transaction usually inputs that should have a matching asset_id - - Returns: - str: uuid of the asset. - - Raises: - AssetIdMismatch: If the inputs are related to different assets. - """ - - if not isinstance(transactions, list): - transactions = [transactions] - - # create a set of asset_ids - asset_ids = {tx.asset.data_id for tx in transactions} - - # check that all the transasctions have the same asset_id - if len(asset_ids) > 1: - raise AssetIdMismatch("All inputs of a transaction need to have the same asset id.") - return asset_ids.pop() - - class Transaction(Transaction): def validate(self, bigchain): """Validate a transaction. @@ -71,7 +41,8 @@ class Transaction(Transaction): if inputs_defined: raise ValueError('A CREATE operation has no inputs') # validate asset - self.asset._validate_asset() + amount = sum([condition.amount for condition in self.conditions]) + self.asset.validate_asset(amount=amount) elif self.operation == Transaction.TRANSFER: if not inputs_defined: raise ValueError('Only `CREATE` transactions can have null ' @@ -79,6 +50,7 @@ class Transaction(Transaction): # check inputs # store the inputs so that we can check if the asset ids match input_txs = [] + input_amount = 0 for ffill in self.fulfillments: input_txid = ffill.tx_input.txid input_cid = ffill.tx_input.cid @@ -101,11 +73,34 @@ class Transaction(Transaction): input_conditions.append(input_tx.conditions[input_cid]) input_txs.append(input_tx) + if input_tx.conditions[input_cid].amount < 1: + raise AmountError('`amount` needs to be greater than zero') + input_amount += input_tx.conditions[input_cid].amount # validate asset id asset_id = Asset.get_asset_id(input_txs) if asset_id != self.asset.data_id: - raise AssetIdMismatch('The asset id of the input does not match the asset id of the transaction') + raise AssetIdMismatch(('The asset id of the input does not' + ' match the asset id of the' + ' transaction')) + + # get the asset creation to see if its divisible or not + asset = bigchain.get_asset_by_id(asset_id) + # validate the asset + asset.validate_asset(amount=input_amount) + # validate the amounts + output_amount = 0 + for condition in self.conditions: + if condition.amount < 1: + raise AmountError('`amount` needs to be greater than zero') + output_amount += condition.amount + + if output_amount != input_amount: + raise AmountError(('The amount used in the inputs `{}`' + ' needs to be same as the amount used' + ' in the outputs `{}`') + .format(input_amount, output_amount)) + else: allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS) raise TypeError('`operation`: `{}` must be either {}.' diff --git a/bigchaindb/pipelines/vote.py b/bigchaindb/pipelines/vote.py index 6b12f55b..b89e0786 100644 --- a/bigchaindb/pipelines/vote.py +++ b/bigchaindb/pipelines/vote.py @@ -40,10 +40,11 @@ class Vote: self.validity = {} self.invalid_dummy_tx = Transaction.create([self.bigchain.me], - [self.bigchain.me]) + [([self.bigchain.me], 1)]) def validate_block(self, block): - if not self.bigchain.has_previous_vote(block['id'], block['block']['voters']): + if not self.bigchain.has_previous_vote(block['id'], + block['block']['voters']): try: block = Block.from_dict(block) except (exceptions.InvalidHash, exceptions.InvalidSignature): diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index e18684c5..56a08d53 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -1,5 +1,7 @@ import pytest -from ..db.conftest import inputs +from unittest.mock import patch + +from ..db.conftest import inputs # noqa @pytest.mark.usefixtures('inputs') @@ -9,7 +11,7 @@ def test_asset_transfer(b, user_vk, user_sk): tx_input = b.get_owned_ids(user_vk).pop() tx_create = b.get_transaction(tx_input.txid) - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_vk], + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)], tx_create.asset) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -18,61 +20,40 @@ def test_asset_transfer(b, user_vk, user_sk): def test_validate_bad_asset_creation(b, user_vk): - from bigchaindb.models import Transaction + from bigchaindb.models import Transaction, Asset # `divisible` needs to be a boolean - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx.asset.divisible = 1 - tx_signed = tx.sign([b.me_private]) + with patch.object(Asset, 'validate_asset', return_value=None): + tx_signed = tx.sign([b.me_private]) with pytest.raises(TypeError): tx_signed.validate(b) # `refillable` needs to be a boolean - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx.asset.refillable = 1 - tx_signed = tx.sign([b.me_private]) + with patch.object(Asset, 'validate_asset', return_value=None): + tx_signed = tx.sign([b.me_private]) with pytest.raises(TypeError): b.validate_transaction(tx_signed) # `updatable` needs to be a boolean - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx.asset.updatable = 1 - tx_signed = tx.sign([b.me_private]) + with patch.object(Asset, 'validate_asset', return_value=None): + tx_signed = tx.sign([b.me_private]) with pytest.raises(TypeError): b.validate_transaction(tx_signed) # `data` needs to be a dictionary - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx.asset.data = 'a' - tx_signed = tx.sign([b.me_private]) + with patch.object(Asset, 'validate_asset', return_value=None): + tx_signed = tx.sign([b.me_private]) with pytest.raises(TypeError): b.validate_transaction(tx_signed) - # TODO: Check where to test for the amount - """ - tx = b.create_transaction(b.me, user_vk, None, 'CREATE') - tx['transaction']['conditions'][0]['amount'] = 'a' - tx['id'] = get_hash_data(tx['transaction']) - tx_signed = b.sign_transaction(tx, b.me_private) - with pytest.raises(TypeError): - b.validate_transaction(tx_signed) - - tx = b.create_transaction(b.me, user_vk, None, 'CREATE') - tx['transaction']['conditions'][0]['amount'] = 2 - tx['transaction']['asset'].update({'divisible': False}) - tx['id'] = get_hash_data(tx['transaction']) - tx_signed = b.sign_transaction(tx, b.me_private) - with pytest.raises(AmountError): - b.validate_transaction(tx_signed) - - tx = b.create_transaction(b.me, user_vk, None, 'CREATE') - tx['transaction']['conditions'][0]['amount'] = 0 - tx['id'] = get_hash_data(tx['transaction']) - tx_signed = b.sign_transaction(tx, b.me_private) - with pytest.raises(AmountError): - b.validate_transaction(tx_signed) - """ - @pytest.mark.usefixtures('inputs') def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk): @@ -81,7 +62,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk): tx_create = b.get_owned_ids(user_vk).pop() tx_create = b.get_transaction(tx_create.txid) - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_vk], + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)], tx_create.asset) tx_transfer.asset.data_id = 'aaa' tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -92,7 +73,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk): def test_get_asset_id_create_transaction(b, user_vk): from bigchaindb.models import Transaction, Asset - tx_create = Transaction.create([b.me], [user_vk]) + tx_create = Transaction.create([b.me], [([user_vk], 1)]) asset_id = Asset.get_asset_id(tx_create) assert asset_id == tx_create.asset.data_id @@ -105,7 +86,7 @@ def test_get_asset_id_transfer_transaction(b, user_vk, user_sk): tx_create = b.get_owned_ids(user_vk).pop() tx_create = b.get_transaction(tx_create.txid) # create a transfer transaction - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_vk], + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)], tx_create.asset) tx_transfer_signed = tx_transfer.sign([user_sk]) # create a block @@ -123,8 +104,8 @@ def test_asset_id_mismatch(b, user_vk): from bigchaindb.models import Transaction, Asset from bigchaindb.common.exceptions import AssetIdMismatch - tx1 = Transaction.create([b.me], [user_vk]) - tx2 = Transaction.create([b.me], [user_vk]) + tx1 = Transaction.create([b.me], [([user_vk], 1)]) + tx2 = Transaction.create([b.me], [([user_vk], 1)]) with pytest.raises(AssetIdMismatch): Asset.get_asset_id([tx1, tx2]) @@ -144,7 +125,7 @@ def test_get_txs_by_asset_id(b, user_vk, user_sk): assert txs[0].asset.data_id == asset_id # create a transfer transaction - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_vk], + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)], tx_create.asset) tx_transfer_signed = tx_transfer.sign([user_sk]) # create the block @@ -161,3 +142,73 @@ def test_get_txs_by_asset_id(b, user_vk, user_sk): assert tx_transfer.id in [t.id for t in txs] assert asset_id == txs[0].asset.data_id assert asset_id == txs[1].asset.data_id + + +@pytest.mark.usefixtures('inputs') +def test_get_asset_by_id(b, user_vk, user_sk): + from bigchaindb.models import Transaction + + tx_create = b.get_owned_ids(user_vk).pop() + tx_create = b.get_transaction(tx_create.txid) + asset_id = tx_create.asset.data_id + + # create a transfer transaction + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)], + tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + # create the block + block = b.create_block([tx_transfer_signed]) + b.write_block(block, durability='hard') + # vote the block valid + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + txs = b.get_txs_by_asset_id(asset_id) + assert len(txs) == 2 + + asset = b.get_asset_by_id(asset_id) + assert asset == tx_create.asset + + +def test_create_invalid_divisible_asset(b, user_vk, user_sk): + from bigchaindb.models import Transaction, Asset + from bigchaindb.common.exceptions import AmountError + + # non divisible assets cannot have amount > 1 + # Transaction.__init__ should raise an exception + asset = Asset(divisible=False) + with pytest.raises(AmountError): + Transaction.create([user_vk], [([user_vk], 2)], asset=asset) + + # divisible assets need to have an amount > 1 + # Transaction.__init__ should raise an exception + asset = Asset(divisible=True) + with pytest.raises(AmountError): + Transaction.create([user_vk], [([user_vk], 1)], asset=asset) + + # even if a transaction is badly constructed the server should raise the + # exception + asset = Asset(divisible=False) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction.create([user_vk], [([user_vk], 2)], asset=asset) + tx_signed = tx.sign([user_sk]) + with pytest.raises(AmountError): + tx_signed.validate(b) + assert b.is_valid_transaction(tx_signed) is False + + asset = Asset(divisible=True) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction.create([user_vk], [([user_vk], 1)], asset=asset) + tx_signed = tx.sign([user_sk]) + with pytest.raises(AmountError): + tx_signed.validate(b) + assert b.is_valid_transaction(tx_signed) is False + + +def test_create_valid_divisible_asset(b, user_vk, user_sk): + from bigchaindb.models import Transaction, Asset + + asset = Asset(divisible=True) + tx = Transaction.create([user_vk], [([user_vk], 2)], asset=asset) + tx_signed = tx.sign([user_sk]) + assert b.is_valid_transaction(tx_signed) diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py new file mode 100644 index 00000000..5ae360e0 --- /dev/null +++ b/tests/assets/test_divisible_assets.py @@ -0,0 +1,779 @@ +import pytest + +from unittest.mock import patch + +from ..db.conftest import inputs # noqa + + +# CREATE divisible asset +# Single input +# Single owners_before +# Single output +# Single owners_after +def test_single_in_single_own_single_out_single_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 1 + assert tx_signed.conditions[0].amount == 100 + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Single owners_before +# Multiple outputs +# Single owners_after per output +def test_single_in_single_own_multiple_out_single_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)], + asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 2 + assert tx_signed.conditions[0].amount == 50 + assert tx_signed.conditions[1].amount == 50 + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Single owners_before +# Single output +# Multiple owners_after +def test_single_in_single_own_single_out_multiple_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], [([user_vk, user_vk], 100)], asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 1 + assert tx_signed.conditions[0].amount == 100 + + condition = tx_signed.conditions[0].to_dict() + assert 'subfulfillments' in condition['condition']['details'] + assert len(condition['condition']['details']['subfulfillments']) == 2 + + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Single owners_before +# Multiple outputs +# Mix: one output with a single owners_after, one output with multiple +# owners_after +def test_single_in_single_own_multiple_out_mix_own_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me], + [([user_vk], 50), ([user_vk, user_vk], 50)], + asset=asset) + tx_signed = tx.sign([b.me_private]) + + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 2 + assert tx_signed.conditions[0].amount == 50 + assert tx_signed.conditions[1].amount == 50 + + condition_cid1 = tx_signed.conditions[1].to_dict() + assert 'subfulfillments' in condition_cid1['condition']['details'] + assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 + + assert len(tx_signed.fulfillments) == 1 + + +# CREATE divisible asset +# Single input +# Multiple owners_before +# Output combinations already tested above +def test_single_in_multiple_own_single_out_single_own_create(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + asset = Asset(divisible=True) + tx = Transaction.create([b.me, user_vk], [([user_vk], 100)], asset=asset) + tx_signed = tx.sign([b.me_private, user_sk]) + assert tx_signed.validate(b) == tx_signed + assert len(tx_signed.conditions) == 1 + assert tx_signed.conditions[0].amount == 100 + assert len(tx_signed.fulfillments) == 1 + + ffill = tx_signed.fulfillments[0].fulfillment.to_dict() + assert 'subfulfillments' in ffill + assert len(ffill['subfulfillments']) == 2 + + +# TRANSFER divisible asset +# Single input +# Single owners_before +# Single output +# Single owners_after +# TODO: I don't really need inputs. But I need the database to be setup or +# else there will be no genesis block and b.get_last_voted_block will +# fail. +# Is there a better way of doing this? +@pytest.mark.usefixtures('inputs') +def test_single_in_single_own_single_out_single_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 100 + assert len(tx_transfer_signed.fulfillments) == 1 + + +# TRANSFER divisible asset +# Single input +# Single owners_before +# Multiple output +# Single owners_after +@pytest.mark.usefixtures('inputs') +def test_single_in_single_own_multiple_out_single_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me], 50), ([b.me], 50)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 2 + assert tx_transfer_signed.conditions[0].amount == 50 + assert tx_transfer_signed.conditions[1].amount == 50 + assert len(tx_transfer_signed.fulfillments) == 1 + + +# TRANSFER divisible asset +# Single input +# Single owners_before +# Single output +# Multiple owners_after +@pytest.mark.usefixtures('inputs') +def test_single_in_single_own_single_out_multiple_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me, b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 100 + + condition = tx_transfer_signed.conditions[0].to_dict() + assert 'subfulfillments' in condition['condition']['details'] + assert len(condition['condition']['details']['subfulfillments']) == 2 + + assert len(tx_transfer_signed.fulfillments) == 1 + + +# TRANSFER divisible asset +# Single input +# Single owners_before +# Multiple outputs +# Mix: one output with a single owners_after, one output with multiple +# owners_after +@pytest.mark.usefixtures('inputs') +def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me], 50), ([b.me, b.me], 50)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 2 + assert tx_transfer_signed.conditions[0].amount == 50 + assert tx_transfer_signed.conditions[1].amount == 50 + + condition_cid1 = tx_transfer_signed.conditions[1].to_dict() + assert 'subfulfillments' in condition_cid1['condition']['details'] + assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 + + assert len(tx_transfer_signed.fulfillments) == 1 + + +# TRANSFER divisible asset +# Single input +# Multiple owners_before +# Single output +# Single owners_after +@pytest.mark.usefixtures('inputs') +def test_single_in_multiple_own_single_out_single_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([b.me, user_vk], 100)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 100 + assert len(tx_transfer_signed.fulfillments) == 1 + + ffill = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() + assert 'subfulfillments' in ffill + assert len(ffill['subfulfillments']) == 2 + + +# TRANSFER divisible asset +# Multiple inputs +# Single owners_before per input +# Single output +# Single owners_after +@pytest.mark.usefixtures('inputs') +def test_multiple_in_single_own_single_out_single_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 100 + assert len(tx_transfer_signed.fulfillments) == 2 + + +# TRANSFER divisible asset +# Multiple inputs +# Multiple owners_before per input +# Single output +# Single owners_after +@pytest.mark.usefixtures('inputs') +def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk, b.me], 50), + ([user_vk, b.me], 50)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) + + assert tx_transfer_signed.validate(b) + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 100 + assert len(tx_transfer_signed.fulfillments) == 2 + + ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() + ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() + assert 'subfulfillments' in ffill_fid0 + assert 'subfulfillments' in ffill_fid1 + assert len(ffill_fid0['subfulfillments']) == 2 + assert len(ffill_fid1['subfulfillments']) == 2 + + +# TRANSFER divisible asset +# Multiple inputs +# Mix: one input with a single owners_before, one input with multiple +# owners_before +# Single output +# Single owners_after +@pytest.mark.usefixtures('inputs') +def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk], 50), + ([user_vk, b.me], 50)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 100 + assert len(tx_transfer_signed.fulfillments) == 2 + + ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() + ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() + assert 'subfulfillments' not in ffill_fid0 + assert 'subfulfillments' in ffill_fid1 + assert len(ffill_fid1['subfulfillments']) == 2 + + +# TRANSFER divisible asset +# Multiple inputs +# Mix: one input with a single owners_before, one input with multiple +# owners_before +# Multiple outputs +# Mix: one output with a single owners_after, one output with multiple +# owners_after +@pytest.mark.usefixtures('inputs') +def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_vk, + user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk], 50), + ([user_vk, b.me], 50)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me], 50), ([b.me, user_vk], 50)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 2 + assert tx_transfer_signed.conditions[0].amount == 50 + assert tx_transfer_signed.conditions[1].amount == 50 + assert len(tx_transfer_signed.fulfillments) == 2 + + cond_cid0 = tx_transfer_signed.conditions[0].to_dict() + cond_cid1 = tx_transfer_signed.conditions[1].to_dict() + assert 'subfulfillments' not in cond_cid0['condition']['details'] + assert 'subfulfillments' in cond_cid1['condition']['details'] + assert len(cond_cid1['condition']['details']['subfulfillments']) == 2 + + ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() + ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() + assert 'subfulfillments' not in ffill_fid0 + assert 'subfulfillments' in ffill_fid1 + assert len(ffill_fid1['subfulfillments']) == 2 + + +# TRANSFER divisible asset +# Multiple inputs from different transactions +# Single owners_before +# Single output +# Single owners_after +@pytest.mark.usefixtures('inputs') +def test_multiple_in_different_transactions(b, user_vk, user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + # `b` creates a divisible asset and assigns 50 shares to `b` and + # 50 shares to `user_vk` + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk], 50), + ([b.me], 50)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER divisible asset + # `b` transfers its 50 shares to `user_vk` + # after this transaction `user_vk` will have a total of 100 shares + # split across two different transactions + tx_transfer1 = Transaction.transfer(tx_create.to_inputs([1]), + [([user_vk], 50)], + asset=tx_create.asset) + tx_transfer1_signed = tx_transfer1.sign([b.me_private]) + # create block + block = b.create_block([tx_transfer1_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + # `user_vk` combines two different transaction with 50 shares each and + # transfers a total of 100 shares back to `b` + tx_transfer2 = Transaction.transfer(tx_create.to_inputs([0]) + + tx_transfer1.to_inputs([0]), + [([b.me], 100)], + asset=tx_create.asset) + tx_transfer2_signed = tx_transfer2.sign([user_sk]) + + assert tx_transfer2_signed.validate(b) == tx_transfer2_signed + assert len(tx_transfer2_signed.conditions) == 1 + assert tx_transfer2_signed.conditions[0].amount == 100 + assert len(tx_transfer2_signed.fulfillments) == 2 + + fid0_input = tx_transfer2_signed.fulfillments[0].to_dict()['input']['txid'] + fid1_input = tx_transfer2_signed.fulfillments[1].to_dict()['input']['txid'] + assert fid0_input == tx_create.id + assert fid1_input == tx_transfer1.id + + +# In a TRANSFER transaction of a divisible asset the amount being spent in the +# inputs needs to match the amount being sent in the outputs. +# In other words `amount_in_inputs - amount_in_outputs == 0` +@pytest.mark.usefixtures('inputs') +def test_amount_error_transfer(b, user_vk, user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + # output amount less than input amount + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + with pytest.raises(AmountError): + tx_transfer_signed.validate(b) + + # TRANSFER + # output amount greater than input amount + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 101)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + with pytest.raises(AmountError): + tx_transfer_signed.validate(b) + + +@pytest.mark.skip(reason='Figure out how to handle this case') +@pytest.mark.usefixtures('inputs') +def test_threshold_same_public_key(b, user_vk, user_sk): + # If we try to fulfill a threshold condition where each subcondition has + # the same key get_subcondition_from_vk will always return the first + # subcondition. This means that only the 1st subfulfillment will be + # generated + # Creating threshold conditions with the same key does not make sense but + # that does not mean that the code shouldn't work. + + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk, user_vk], 100)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # TRANSFER + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk, user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + + +@pytest.mark.usefixtures('inputs') +def test_sum_amount(b, user_vk, user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset with 3 outputs with amount 1 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk], 1), + ([user_vk], 1), + ([user_vk], 1)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # create a transfer transaction with one output and check if the amount + # is 3 + tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 3)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 1 + assert tx_transfer_signed.conditions[0].amount == 3 + + +@pytest.mark.usefixtures('inputs') +def test_divide(b, user_vk, user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # create a transfer transaction with 3 outputs and check if the amount + # of each output is 1 + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me], 1), ([b.me], 1), ([b.me], 1)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + + assert tx_transfer_signed.validate(b) == tx_transfer_signed + assert len(tx_transfer_signed.conditions) == 3 + for condition in tx_transfer_signed.conditions: + assert condition.amount == 1 + + +# Check that negative inputs are caught when creating a TRANSFER transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_transfer(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + with pytest.raises(AmountError): + Transaction.transfer(tx_create.to_inputs(), + [([b.me], 4), ([b.me], -1)], + asset=tx_create.asset) + + +# Check that negative inputs are caught when validating a TRANSFER transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_transfer_validate(b, user_vk, user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # create a transfer transaction with 3 outputs and check if the amount + # of each output is 1 + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me], 4), ([b.me], 1)], + asset=tx_create.asset) + tx_transfer.conditions[1].amount = -1 + tx_transfer_signed = tx_transfer.sign([user_sk]) + + with pytest.raises(AmountError): + tx_transfer_signed.validate(b) + + +# Check that negative inputs are caught when creating a CREATE transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + with pytest.raises(AmountError): + Transaction.create([b.me], [([user_vk], -3)], + asset=asset) + + +# Check that negative inputs are caught when validating a CREATE transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_create_validate(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create.conditions[0].amount = -3 + with patch.object(Asset, 'validate_asset', return_value=None): + tx_create_signed = tx_create.sign([b.me_private]) + + with pytest.raises(AmountError): + tx_create_signed.validate(b) diff --git a/tests/common/test_asset.py b/tests/common/test_asset.py index edfbcb5f..f6a3f89d 100644 --- a/tests/common/test_asset.py +++ b/tests/common/test_asset.py @@ -22,6 +22,7 @@ def test_asset_creation_with_data(data): def test_asset_invalid_asset_initialization(): from bigchaindb.common.transaction import Asset + # check types with raises(TypeError): Asset(data='some wrong type') with raises(TypeError): @@ -31,6 +32,12 @@ def test_asset_invalid_asset_initialization(): with raises(TypeError): Asset(updatable=1) + # check for features that are not yet implemented + with raises(NotImplementedError): + Asset(updatable=True) + with raises(NotImplementedError): + Asset(refillable=True) + def test_invalid_asset_comparison(data, data_id): from bigchaindb.common.transaction import Asset @@ -69,12 +76,17 @@ def test_asset_deserialization(data, data_id): def test_validate_asset(): from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + # test amount errors + asset = Asset(divisible=False) + with raises(AmountError): + asset.validate_asset(amount=2) + + asset = Asset(divisible=True) + with raises(AmountError): + asset.validate_asset(amount=1) + + asset = Asset() with raises(TypeError): - Asset(divisible=1) - with raises(TypeError): - Asset(refillable=1) - with raises(TypeError): - Asset(updatable=1) - with raises(TypeError): - Asset(data='we need more lemon pledge') + asset.validate_asset(amount='a') diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index 5f2d58fb..baba35af 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -1,4 +1,5 @@ -from pytest import raises, mark +from pytest import raises +from unittest.mock import patch def test_fulfillment_serialization(ffill_uri, user_pub): @@ -166,29 +167,7 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub, expected_threshold.add_subfulfillment(expected_simple3) expected.add_subfulfillment(expected_threshold) - cond = Condition.generate([user_pub, [user2_pub, expected_simple3]]) - assert cond.fulfillment.to_dict() == expected.to_dict() - - -def test_generate_conditions_split_half_recursive_custom_threshold(user_pub, - user2_pub, - user3_pub): - from bigchaindb.common.transaction import Condition - from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment - - expected_simple1 = Ed25519Fulfillment(public_key=user_pub) - expected_simple2 = Ed25519Fulfillment(public_key=user2_pub) - expected_simple3 = Ed25519Fulfillment(public_key=user3_pub) - - expected = ThresholdSha256Fulfillment(threshold=1) - expected.add_subfulfillment(expected_simple1) - expected_threshold = ThresholdSha256Fulfillment(threshold=1) - expected_threshold.add_subfulfillment(expected_simple2) - expected_threshold.add_subfulfillment(expected_simple3) - expected.add_subfulfillment(expected_threshold) - - cond = Condition.generate(([user_pub, ([user2_pub, expected_simple3], 1)], - 1)) + cond = Condition.generate([user_pub, [user2_pub, expected_simple3]], 1) assert cond.fulfillment.to_dict() == expected.to_dict() @@ -208,7 +187,7 @@ def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, expected.add_subfulfillment(expected_threshold) expected.add_subfulfillment(expected_simple1) - cond = Condition.generate([[expected_simple2, user3_pub], user_pub]) + cond = Condition.generate([[expected_simple2, user3_pub], user_pub], 1) assert cond.fulfillment.to_dict() == expected.to_dict() @@ -225,7 +204,7 @@ def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub): expected.add_subfulfillment(expected_simple2) expected.add_subfulfillment(expected_simple3) - cond = Condition.generate([user_pub, user2_pub, expected_simple3]) + cond = Condition.generate([user_pub, user2_pub, expected_simple3], 1) assert cond.fulfillment.to_dict() == expected.to_dict() @@ -234,7 +213,7 @@ def test_generate_conditions_single_owner(user_pub): from cryptoconditions import Ed25519Fulfillment expected = Ed25519Fulfillment(public_key=user_pub) - cond = Condition.generate([user_pub]) + cond = Condition.generate([user_pub], 1) assert cond.fulfillment.to_dict() == expected.to_dict() @@ -244,48 +223,23 @@ def test_generate_conditions_single_owner_with_condition(user_pub): from cryptoconditions import Ed25519Fulfillment expected = Ed25519Fulfillment(public_key=user_pub) - cond = Condition.generate([expected]) + cond = Condition.generate([expected], 1) assert cond.fulfillment.to_dict() == expected.to_dict() -# TODO FOR CC: see skip reason -@mark.skip(reason='threshold(hashlock).to_dict() exposes secret') -def test_generate_threshold_condition_with_hashlock(user_pub, user2_pub, - user3_pub): - from bigchaindb.common.transaction import Condition - from cryptoconditions import (PreimageSha256Fulfillment, - Ed25519Fulfillment, - ThresholdSha256Fulfillment) - - secret = b'much secret, wow' - hashlock = PreimageSha256Fulfillment(preimage=secret) - - expected_simple1 = Ed25519Fulfillment(public_key=user_pub) - expected_simple3 = Ed25519Fulfillment(public_key=user3_pub) - - expected = ThresholdSha256Fulfillment(threshold=2) - expected_sub = ThresholdSha256Fulfillment(threshold=2) - expected_sub.add_subfulfillment(expected_simple1) - expected_sub.add_subfulfillment(hashlock) - expected.add_subfulfillment(expected_simple3) - - cond = Condition.generate([[user_pub, hashlock], expected_simple3]) - assert cond.fulfillment.to_dict() == expected.to_dict() - - def test_generate_conditions_invalid_parameters(user_pub, user2_pub, user3_pub): from bigchaindb.common.transaction import Condition with raises(ValueError): - Condition.generate([]) + Condition.generate([], 1) with raises(TypeError): - Condition.generate('not a list') + Condition.generate('not a list', 1) with raises(ValueError): - Condition.generate([[user_pub, [user2_pub, [user3_pub]]]]) + Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1) with raises(ValueError): - Condition.generate([[user_pub]]) + Condition.generate([[user_pub]], 1) def test_invalid_transaction_initialization(): @@ -321,7 +275,8 @@ def test_invalid_transaction_initialization(): def test_create_default_asset_on_tx_initialization(): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, None) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, None) expected = Asset() asset = tx.asset @@ -510,10 +465,62 @@ def test_cast_transaction_link_to_boolean(): assert bool(TransactionLink(False, False)) is True +def test_asset_link_serialization(): + from bigchaindb.common.transaction import AssetLink + + data_id = 'a asset id' + expected = { + 'id': data_id, + } + asset_link = AssetLink(data_id) + + assert asset_link.to_dict() == expected + + +def test_asset_link_serialization_with_empty_payload(): + from bigchaindb.common.transaction import AssetLink + + expected = None + asset_link = AssetLink() + + assert asset_link.to_dict() == expected + + +def test_asset_link_deserialization(): + from bigchaindb.common.transaction import AssetLink + + data_id = 'a asset id' + expected = AssetLink(data_id) + asset_link = { + 'id': data_id + } + asset_link = AssetLink.from_dict(asset_link) + + assert asset_link == expected + + +def test_asset_link_deserialization_with_empty_payload(): + from bigchaindb.common.transaction import AssetLink + + expected = AssetLink() + asset_link = AssetLink.from_dict(None) + + assert asset_link == expected + + +def test_cast_asset_link_to_boolean(): + from bigchaindb.common.transaction import AssetLink + + assert bool(AssetLink()) is False + assert bool(AssetLink('a')) is True + assert bool(AssetLink(False)) is True + + def test_add_fulfillment_to_tx(user_ffill): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, Asset(), [], []) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, Asset(), [], []) tx.add_fulfillment(user_ffill) assert len(tx.fulfillments) == 1 @@ -522,7 +529,8 @@ def test_add_fulfillment_to_tx(user_ffill): def test_add_fulfillment_to_tx_with_invalid_parameters(): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, Asset()) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, Asset()) with raises(TypeError): tx.add_fulfillment('somewronginput') @@ -530,7 +538,8 @@ def test_add_fulfillment_to_tx_with_invalid_parameters(): def test_add_condition_to_tx(user_cond): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, Asset()) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, Asset()) tx.add_condition(user_cond) assert len(tx.conditions) == 1 @@ -539,7 +548,8 @@ def test_add_condition_to_tx(user_cond): def test_add_condition_to_tx_with_invalid_parameters(): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, Asset(), [], []) + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, Asset(), [], []) with raises(TypeError): tx.add_condition('somewronginput') @@ -614,16 +624,14 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): from bigchaindb.common.crypto import SigningKey from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, Asset(), + tx = Transaction(Transaction.CREATE, Asset(divisible=True), [user_ffill, deepcopy(user_ffill)], - [user_ffill, deepcopy(user_cond)]) + [user_cond, deepcopy(user_cond)]) expected_first = deepcopy(tx) expected_second = deepcopy(tx) expected_first.fulfillments = [expected_first.fulfillments[0]] - expected_first.conditions = [expected_first.conditions[0]] expected_second.fulfillments = [expected_second.fulfillments[1]] - expected_second.conditions = [expected_second.conditions[1]] expected_first_bytes = str(expected_first).encode() expected_first.fulfillments[0].fulfillment.sign(expected_first_bytes, @@ -674,7 +682,7 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, Fulfillment, Condition, Asset) from cryptoconditions import Ed25519Fulfillment - tx = Transaction(Transaction.CREATE, Asset(), + tx = Transaction(Transaction.CREATE, Asset(divisible=True), [user_ffill, deepcopy(user_ffill)], [user_cond, deepcopy(user_cond)]) tx.sign([user_priv]) @@ -715,10 +723,6 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, with raises(TypeError): transfer_tx.operation = "Operation that doesn't exist" transfer_tx.fulfillments_valid([utx.conditions[0]]) - with raises(ValueError): - tx = utx.sign([user_priv]) - tx.conditions = [] - tx.fulfillments_valid() def test_create_create_transaction_single_io(user_cond, user_pub, data, @@ -754,7 +758,8 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data, } asset = Asset(data, data_id) - tx = Transaction.create([user_pub], [user_pub], data, asset).to_dict() + tx = Transaction.create([user_pub], [([user_pub], 1)], + data, asset).to_dict() tx.pop('id') tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') @@ -766,17 +771,20 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data, def test_validate_single_io_create_transaction(user_pub, user_priv, data): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction.create([user_pub], [user_pub], data, Asset()) + tx = Transaction.create([user_pub], [([user_pub], 1)], data, Asset()) tx = tx.sign([user_priv]) assert tx.fulfillments_valid() is True -@mark.skip(reason='Multiple inputs and outputs in CREATE not supported') -# TODO: Add digital assets def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, user2_pub): - from bigchaindb.common.transaction import Transaction + from bigchaindb.common.transaction import Transaction, Asset, Fulfillment + # a fulfillment for a create transaction with multiple `owners_before` + # is a fulfillment for an implicit threshold condition with + # weight = len(owners_before) + ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict() + ffill.update({'fid': 0}) expected = { 'transaction': { 'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)], @@ -785,45 +793,32 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, 'message': 'hello' } }, - 'fulfillments': [ - { - 'owners_before': [ - user_pub, - ], - 'fid': 0, - 'fulfillment': None, - 'input': None - }, - { - 'owners_before': [ - user2_pub, - ], - 'fid': 1, - 'fulfillment': None, - 'input': None - } - ], + 'fulfillments': [ffill], 'operation': 'CREATE', }, 'version': 1 } - tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], - {'message': 'hello'}).to_dict() + asset = Asset(divisible=True) + tx = Transaction.create([user_pub, user2_pub], + [([user_pub], 1), ([user2_pub], 1)], + asset=asset, + metadata={'message': 'hello'}).to_dict() tx.pop('id') tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') + tx['transaction'].pop('asset') assert tx == expected -@mark.skip(reason='Multiple inputs and outputs in CREATE not supported') -# TODO: Add digital assets def test_validate_multiple_io_create_transaction(user_pub, user_priv, user2_pub, user2_priv): - from bigchaindb.common.transaction import Transaction + from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], - {'message': 'hello'}) + tx = Transaction.create([user_pub, user2_pub], + [([user_pub], 1), ([user2_pub], 1)], + metadata={'message': 'hello'}, + asset=Asset(divisible=True)) tx = tx.sign([user_priv, user2_priv]) assert tx.fulfillments_valid() is True @@ -862,7 +857,8 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, 'version': 1 } asset = Asset(data, data_id) - tx = Transaction.create([user_pub], [user_pub, user2_pub], data, asset) + tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], + data, asset) tx_dict = tx.to_dict() tx_dict.pop('id') tx_dict['transaction']['metadata'].pop('id') @@ -876,80 +872,27 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, data): from bigchaindb.common.transaction import Transaction, Asset - tx = Transaction.create([user_pub], [user_pub, user2_pub], data, Asset()) + tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], + data, Asset()) tx = tx.sign([user_priv]) assert tx.fulfillments_valid() is True -def test_create_create_transaction_hashlock(user_pub, data, data_id): - from cryptoconditions import PreimageSha256Fulfillment - from bigchaindb.common.transaction import Transaction, Condition, Asset - - secret = b'much secret, wow' - hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri - cond = Condition(hashlock) - - expected = { - 'transaction': { - 'conditions': [cond.to_dict(0)], - 'metadata': { - 'data': data, - }, - 'asset': { - 'id': data_id, - 'divisible': False, - 'updatable': False, - 'refillable': False, - 'data': data, - }, - 'fulfillments': [ - { - 'owners_before': [ - user_pub, - ], - 'fid': 0, - 'fulfillment': None, - 'input': None - }, - ], - 'operation': 'CREATE', - }, - 'version': 1 - } - - asset = Asset(data, data_id) - tx = Transaction.create([user_pub], [], data, asset, secret).to_dict() - tx.pop('id') - tx['transaction']['metadata'].pop('id') - tx['transaction'].pop('timestamp') - tx['transaction']['fulfillments'][0]['fulfillment'] = None - - assert tx == expected - - -def test_validate_hashlock_create_transaction(user_pub, user_priv, data): - from bigchaindb.common.transaction import Transaction, Asset - - tx = Transaction.create([user_pub], [], data, Asset(), b'much secret, wow') - tx = tx.sign([user_priv]) - assert tx.fulfillments_valid() is True - - -def test_create_create_transaction_with_invalid_parameters(): +def test_create_create_transaction_with_invalid_parameters(user_pub): from bigchaindb.common.transaction import Transaction with raises(TypeError): Transaction.create('not a list') with raises(TypeError): Transaction.create([], 'not a list') - with raises(NotImplementedError): - Transaction.create(['a', 'b'], ['c', 'd']) - with raises(NotImplementedError): - Transaction.create(['a'], [], time_expire=123) with raises(ValueError): - Transaction.create(['a'], [], secret=None) + Transaction.create([], [user_pub]) with raises(ValueError): - Transaction.create([], [], secret='wow, much secret') + Transaction.create([user_pub], []) + with raises(ValueError): + Transaction.create([user_pub], [user_pub]) + with raises(ValueError): + Transaction.create([user_pub], [([user_pub],)]) def test_conditions_to_inputs(tx): @@ -995,7 +938,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, } inputs = tx.to_inputs([0]) asset = Asset(None, data_id) - transfer_tx = Transaction.transfer(inputs, [user2_pub], asset=asset) + transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() transfer_tx_body = transfer_tx['transaction'] @@ -1014,16 +957,15 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, assert transfer_tx.fulfillments_valid([tx.conditions[0]]) is True -@mark.skip(reason='FIXME: When divisible assets land') def test_create_transfer_transaction_multiple_io(user_pub, user_priv, user2_pub, user2_priv, user3_pub, user2_cond): - from bigchaindb.common.transaction import Transaction + from bigchaindb.common.transaction import Transaction, Asset - tx1 = Transaction.create([user_pub], [user_pub], {'message': 'hello'}) - tx1 = tx1.sign([user_priv]) - tx2 = Transaction.create([user2_pub], [user2_pub], {'message': 'hello'}) - tx2 = tx2.sign([user2_priv]) + asset = Asset(divisible=True) + tx = Transaction.create([user_pub], [([user_pub], 1), ([user2_pub], 1)], + asset=asset, metadata={'message': 'hello'}) + tx = tx.sign([user_priv]) expected = { 'transaction': { @@ -1037,7 +979,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, 'fid': 0, 'fulfillment': None, 'input': { - 'txid': tx1.id, + 'txid': tx.id, 'cid': 0 } }, { @@ -1047,8 +989,8 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, 'fid': 1, 'fulfillment': None, 'input': { - 'txid': tx2.id, - 'cid': 0 + 'txid': tx.id, + 'cid': 1 } } ], @@ -1056,30 +998,28 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, }, 'version': 1 } - tx1_inputs = tx1.to_inputs() - tx2_inputs = tx2.to_inputs() - tx_inputs = tx1_inputs + tx2_inputs - transfer_tx = Transaction.transfer(tx_inputs, [[user2_pub], [user2_pub]]) + transfer_tx = Transaction.transfer(tx.to_inputs(), + [([user2_pub], 1), ([user2_pub], 1)], + asset=tx.asset) transfer_tx = transfer_tx.sign([user_priv, user2_priv]) - transfer_tx = transfer_tx assert len(transfer_tx.fulfillments) == 2 assert len(transfer_tx.conditions) == 2 - combined_conditions = tx1.conditions + tx2.conditions - assert transfer_tx.fulfillments_valid(combined_conditions) is True + assert transfer_tx.fulfillments_valid(tx.conditions) is True transfer_tx = transfer_tx.to_dict() transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None transfer_tx['transaction'].pop('timestamp') transfer_tx.pop('id') + transfer_tx['transaction'].pop('asset') assert expected == transfer_tx -def test_create_transfer_with_invalid_parameters(): +def test_create_transfer_with_invalid_parameters(user_pub): from bigchaindb.common.transaction import Transaction, Asset with raises(TypeError): @@ -1090,17 +1030,25 @@ def test_create_transfer_with_invalid_parameters(): Transaction.transfer(['fulfillment'], {}, Asset()) with raises(ValueError): Transaction.transfer(['fulfillment'], [], Asset()) + with raises(ValueError): + Transaction.transfer(['fulfillment'], [user_pub], Asset()) + with raises(ValueError): + Transaction.transfer(['fulfillment'], [([user_pub],)], Asset()) def test_cant_add_empty_condition(): - from bigchaindb.common.transaction import Transaction - tx = Transaction(Transaction.CREATE, None) + from bigchaindb.common.transaction import Transaction, Asset + + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, None) with raises(TypeError): tx.add_condition(None) def test_cant_add_empty_fulfillment(): - from bigchaindb.common.transaction import Transaction - tx = Transaction(Transaction.CREATE, None) + from bigchaindb.common.transaction import Transaction, Asset + + with patch.object(Asset, 'validate_asset', return_value=None): + tx = Transaction(Transaction.CREATE, None) with raises(TypeError): tx.add_fulfillment(None) diff --git a/tests/conftest.py b/tests/conftest.py index 58178b7f..fcb6b318 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -72,7 +72,7 @@ def b(request, node_config): @pytest.fixture def create_tx(b, user_vk): from bigchaindb.models import Transaction - return Transaction.create([b.me], [user_vk]) + return Transaction.create([b.me], [([user_vk], 1)]) @pytest.fixture @@ -84,5 +84,5 @@ def signed_create_tx(b, create_tx): def signed_transfer_tx(signed_create_tx, user_vk, user_sk): from bigchaindb.models import Transaction inputs = signed_create_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], signed_create_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], signed_create_tx.asset) return tx.sign([user_sk]) diff --git a/tests/db/conftest.py b/tests/db/conftest.py index fe4508b7..43a352a9 100644 --- a/tests/db/conftest.py +++ b/tests/db/conftest.py @@ -84,7 +84,7 @@ def inputs(user_vk): prev_block_id = g.id for block in range(4): transactions = [ - Transaction.create([b.me], [user_vk]).sign([b.me_private]) + Transaction.create([b.me], [([user_vk], 1)]).sign([b.me_private]) for i in range(10) ] block = b.create_block(transactions) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 6a7880e9..247a31b4 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -15,7 +15,7 @@ def dummy_tx(): import bigchaindb from bigchaindb.models import Transaction b = bigchaindb.Bigchain() - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) return tx @@ -37,7 +37,7 @@ class TestBigchainApi(object): b.create_genesis_block() - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) block1 = b.create_block([tx]) @@ -60,7 +60,7 @@ class TestBigchainApi(object): b.create_genesis_block() - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) block1 = b.create_block([tx]) @@ -74,7 +74,7 @@ class TestBigchainApi(object): b.create_genesis_block() - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) @@ -99,7 +99,7 @@ class TestBigchainApi(object): b.create_genesis_block() - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) @@ -107,13 +107,15 @@ class TestBigchainApi(object): b.write_block(block1, durability='hard') monkeypatch.setattr('time.time', lambda: 2) - transfer_tx = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset) + transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)], + tx.asset) transfer_tx = transfer_tx.sign([b.me_private]) block2 = b.create_block([transfer_tx]) b.write_block(block2, durability='hard') monkeypatch.setattr('time.time', lambda: 3) - transfer_tx2 = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset) + transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)], + tx.asset) transfer_tx2 = transfer_tx2.sign([b.me_private]) block3 = b.create_block([transfer_tx2]) b.write_block(block3, durability='hard') @@ -133,7 +135,7 @@ class TestBigchainApi(object): b.create_genesis_block() - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) @@ -159,13 +161,13 @@ class TestBigchainApi(object): b.create_genesis_block() monkeypatch.setattr('time.time', lambda: 1) - tx1 = Transaction.create([b.me], [b.me]) + tx1 = Transaction.create([b.me], [([b.me], 1)]) tx1 = tx1.sign([b.me_private]) block1 = b.create_block([tx1]) b.write_block(block1, durability='hard') monkeypatch.setattr('time.time', lambda: 2) - tx2 = Transaction.create([b.me], [b.me]) + tx2 = Transaction.create([b.me], [([b.me], 1)]) tx2 = tx2.sign([b.me_private]) block2 = b.create_block([tx2]) b.write_block(block2, durability='hard') @@ -185,7 +187,7 @@ class TestBigchainApi(object): from bigchaindb.models import Transaction metadata = {'msg': 'Hello BigchainDB!'} - tx = Transaction.create([b.me], [user_vk], metadata=metadata) + tx = Transaction.create([b.me], [([user_vk], 1)], metadata=metadata) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -205,7 +207,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) response = b.write_transaction(tx) @@ -223,7 +225,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) b.write_transaction(tx) @@ -243,7 +245,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) # There's no need to b.write_transaction(tx) to the backlog @@ -267,7 +269,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) # Make sure there's a copy of tx in the backlog @@ -537,7 +539,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) b.write_transaction(tx) @@ -563,7 +565,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) b.write_transaction(tx) @@ -587,16 +589,17 @@ class TestBigchainApi(object): fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_vk), [user_vk], TransactionLink('somethingsomething', 0)) - tx = Transaction.transfer([fulfillment], [user_vk], Asset()) + tx = Transaction.transfer([fulfillment], [([user_vk], 1)], Asset()) - with pytest.raises(TransactionDoesNotExist) as excinfo: + with pytest.raises(TransactionDoesNotExist): tx.validate(Bigchain()) def test_count_backlog(self, b, user_vk): from bigchaindb.models import Transaction for _ in range(4): - tx = Transaction.create([b.me], [user_vk]).sign([b.me_private]) + tx = Transaction.create([b.me], + [([user_vk], 1)]).sign([b.me_private]) b.write_transaction(tx) assert b.backend.count_backlog() == 4 @@ -638,7 +641,7 @@ class TestTransactionValidation(object): input_tx = b.get_owned_ids(user_vk).pop() input_transaction = b.get_transaction(input_tx.txid) sk, vk = generate_key_pair() - tx = Transaction.create([vk], [user_vk]) + tx = Transaction.create([vk], [([user_vk], 1)]) tx.operation = 'TRANSFER' tx.asset = input_transaction.asset tx.fulfillments[0].tx_input = input_tx @@ -682,7 +685,8 @@ class TestTransactionValidation(object): input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - transfer_tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + transfer_tx = Transaction.transfer(inputs, [([user_vk], 1)], + input_tx.asset) transfer_tx = transfer_tx.sign([user_sk]) assert transfer_tx == b.validate_transaction(transfer_tx) @@ -706,7 +710,8 @@ class TestTransactionValidation(object): inputs = input_tx.to_inputs() # create a transaction that's valid but not in a voted valid block - transfer_tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) + transfer_tx = Transaction.transfer(inputs, [([user_vk], 1)], + input_tx.asset) transfer_tx = transfer_tx.sign([user_sk]) assert transfer_tx == b.validate_transaction(transfer_tx) @@ -716,8 +721,9 @@ class TestTransactionValidation(object): b.write_block(block, durability='hard') # create transaction with the undecided input - tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [user_vk], - transfer_tx.asset) + tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), + [([user_vk], 1)], + transfer_tx.asset) tx_invalid = tx_invalid.sign([user_sk]) with pytest.raises(FulfillmentNotInValidBlock): @@ -815,7 +821,7 @@ class TestMultipleInputs(object): tx_link = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(tx_link.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [user2_vk], input_tx.asset) + tx = Transaction.transfer(inputs, [([user2_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) # validate transaction @@ -823,69 +829,6 @@ class TestMultipleInputs(object): assert len(tx.fulfillments) == 1 assert len(tx.conditions) == 1 - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) - @pytest.mark.usefixtures('inputs') - def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_vk): - from bigchaindb.common import crypto - from bigchaindb.models import Transaction - - user2_sk, user2_vk = crypto.generate_key_pair() - - # get inputs - owned_inputs = b.get_owned_ids(user_vk) - input_txs = [b.get_transaction(tx_link.txid) for tx_link - in owned_inputs] - inputs = sum([input_tx.to_inputs() for input_tx in input_txs], []) - tx = Transaction.transfer(inputs, len(inputs) * [[user_vk]]) - tx = tx.sign([user_sk]) - assert b.validate_transaction(tx) == tx - assert len(tx.fulfillments) == len(inputs) - assert len(tx.conditions) == len(inputs) - - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) - @pytest.mark.usefixtures('inputs') - def test_transfer_single_owners_single_input_from_multiple_outputs(self, b, - user_sk, - user_vk): - from bigchaindb.common import crypto - from bigchaindb.models import Transaction - - user2_sk, user2_vk = crypto.generate_key_pair() - - # get inputs - owned_inputs = b.get_owned_ids(user_vk) - input_txs = [b.get_transaction(tx_link.txid) for tx_link - in owned_inputs] - inputs = sum([input_tx.to_inputs() for input_tx in input_txs], []) - tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]]) - tx = tx.sign([user_sk]) - - # create block with the transaction - block = b.create_block([tx]) - b.write_block(block, durability='hard') - - # vote block valid - vote = b.vote(block.id, b.get_last_voted_block().id, True) - b.write_vote(vote) - - # get inputs from user2 - owned_inputs = b.get_owned_ids(user2_vk) - assert len(owned_inputs) == len(inputs) - - # create a transaction with a single input from a multiple output transaction - tx_link = owned_inputs.pop() - inputs = b.get_transaction(tx_link.txid).to_inputs([0]) - tx = Transaction.transfer(inputs, [user_vk]) - tx = tx.sign([user2_sk]) - - assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == 1 - assert len(tx.conditions) == 1 - def test_single_owner_before_multiple_owners_after_single_input(self, b, user_sk, user_vk, @@ -899,47 +842,14 @@ class TestMultipleInputs(object): owned_inputs = b.get_owned_ids(user_vk) tx_link = owned_inputs.pop() input_tx = b.get_transaction(tx_link.txid) - tx = Transaction.transfer(input_tx.to_inputs(), [[user2_vk, user3_vk]], input_tx.asset) + tx = Transaction.transfer(input_tx.to_inputs(), + [([user2_vk, user3_vk], 1)], input_tx.asset) tx = tx.sign([user_sk]) assert b.is_valid_transaction(tx) == tx assert len(tx.fulfillments) == 1 assert len(tx.conditions) == 1 - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) - @pytest.mark.usefixtures('inputs') - def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b, - user_sk, - user_vk): - from bigchaindb.common import crypto - from bigchaindb.models import Transaction - - user2_sk, user2_vk = crypto.generate_key_pair() - user3_sk, user3_vk = crypto.generate_key_pair() - - owned_inputs = b.get_owned_ids(user_vk) - input_txs = [b.get_transaction(tx_link.txid) for tx_link - in owned_inputs] - inputs = sum([input_tx.to_inputs() for input_tx in input_txs], []) - - tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk, user3_vk]]) - tx = tx.sign([user_sk]) - - # create block with the transaction - block = b.create_block([tx]) - b.write_block(block, durability='hard') - - # vote block valid - vote = b.vote(block.id, b.get_last_voted_block().id, True) - b.write_vote(vote) - - # validate transaction - assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == len(inputs) - assert len(tx.conditions) == len(inputs) - @pytest.mark.usefixtures('inputs') def test_multiple_owners_before_single_owner_after_single_input(self, b, user_sk, @@ -950,7 +860,7 @@ class TestMultipleInputs(object): user2_sk, user2_vk = crypto.generate_key_pair() user3_sk, user3_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk, user2_vk]) + tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -963,7 +873,8 @@ class TestMultipleInputs(object): input_tx = b.get_transaction(owned_input.txid) inputs = input_tx.to_inputs() - transfer_tx = Transaction.transfer(inputs, [user3_vk], input_tx.asset) + transfer_tx = Transaction.transfer(inputs, [([user3_vk], 1)], + input_tx.asset) transfer_tx = transfer_tx.sign([user_sk, user2_sk]) # validate transaction @@ -971,29 +882,6 @@ class TestMultipleInputs(object): assert len(transfer_tx.fulfillments) == 1 assert len(transfer_tx.conditions) == 1 - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) - @pytest.mark.usefixtures('inputs_shared') - def test_multiple_owners_before_single_owner_after_multiple_inputs(self, b, - user_sk, user_vk, user2_vk, user2_sk): - from bigchaindb.common import crypto - from bigchaindb.models import Transaction - - # create a new users - user3_sk, user3_vk = crypto.generate_key_pair() - - tx_links = b.get_owned_ids(user_vk) - inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link - in tx_links], []) - - tx = Transaction.transfer(inputs, len(inputs) * [[user3_vk]]) - tx = tx.sign([user_sk, user2_sk]) - - assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == len(inputs) - assert len(tx.conditions) == len(inputs) - @pytest.mark.usefixtures('inputs') def test_multiple_owners_before_multiple_owners_after_single_input(self, b, user_sk, @@ -1005,7 +893,7 @@ class TestMultipleInputs(object): user3_sk, user3_vk = crypto.generate_key_pair() user4_sk, user4_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk, user2_vk]) + tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1018,38 +906,14 @@ class TestMultipleInputs(object): tx_link = b.get_owned_ids(user_vk).pop() tx_input = b.get_transaction(tx_link.txid) - tx = Transaction.transfer(tx_input.to_inputs(), [[user3_vk, user4_vk]], tx_input.asset) + tx = Transaction.transfer(tx_input.to_inputs(), + [([user3_vk, user4_vk], 1)], tx_input.asset) tx = tx.sign([user_sk, user2_sk]) assert b.is_valid_transaction(tx) == tx assert len(tx.fulfillments) == 1 assert len(tx.conditions) == 1 - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) - @pytest.mark.usefixtures('inputs_shared') - def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self, b, - user_sk, user_vk, - user2_sk, user2_vk): - from bigchaindb.common import crypto - from bigchaindb.models import Transaction - - # create a new users - user3_sk, user3_vk = crypto.generate_key_pair() - user4_sk, user4_vk = crypto.generate_key_pair() - - tx_links = b.get_owned_ids(user_vk) - inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link - in tx_links], []) - - tx = Transaction.transfer(inputs, len(inputs) * [[user3_vk, user4_vk]]) - tx = tx.sign([user_sk, user2_sk]) - - assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == len(inputs) - assert len(tx.conditions) == len(inputs) - def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_vk): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink @@ -1057,7 +921,7 @@ class TestMultipleInputs(object): user2_sk, user2_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1067,7 +931,7 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [] - tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset) tx = tx.sign([user_sk]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1087,7 +951,7 @@ class TestMultipleInputs(object): genesis = b.create_genesis_block() user2_sk, user2_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1103,7 +967,8 @@ class TestMultipleInputs(object): # NOTE: The transaction itself is valid, still will mark the block # as invalid to mock the behavior. - tx_invalid = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset) + tx_invalid = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], + tx.asset) tx_invalid = tx_invalid.sign([user_sk]) block = b.create_block([tx_invalid]) b.write_block(block, durability='hard') @@ -1119,47 +984,45 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [] - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_vk): - import random from bigchaindb.common import crypto - from bigchaindb.common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink, Asset from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() - transactions = [] - for i in range(2): - payload = {'somedata': random.randint(0, 255)} - tx = Transaction.create([b.me], [user_vk], payload) - tx = tx.sign([b.me_private]) - transactions.append(tx) - block = b.create_block(transactions) + # create divisible asset + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk], 1), ([user_vk], 1)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + block = b.create_block([tx_create_signed]) b.write_block(block, durability='hard') # get input owned_inputs_user1 = b.get_owned_ids(user_vk) owned_inputs_user2 = b.get_owned_ids(user2_vk) - expected_owned_inputs_user1 = [TransactionLink(tx.id, 0) for tx - in transactions] + expected_owned_inputs_user1 = [TransactionLink(tx_create.id, 0), + TransactionLink(tx_create.id, 1)] assert owned_inputs_user1 == expected_owned_inputs_user1 assert owned_inputs_user2 == [] - inputs = sum([tx.to_inputs() for tx in transactions], []) - tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]]) - tx = tx.sign([user_sk]) - block = b.create_block([tx]) + # transfer divisible asset divided in two outputs + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([user2_vk], 1), ([user2_vk], 1)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + block = b.create_block([tx_transfer_signed]) b.write_block(block, durability='hard') owned_inputs_user1 = b.get_owned_ids(user_vk) owned_inputs_user2 = b.get_owned_ids(user2_vk) assert owned_inputs_user1 == [] - assert owned_inputs_user2 == [TransactionLink(tx.id, 0), - TransactionLink(tx.id, 1)] + assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0), + TransactionLink(tx_transfer.id, 1)] def test_get_owned_ids_multiple_owners(self, b, user_sk, user_vk): from bigchaindb.common import crypto @@ -1169,7 +1032,7 @@ class TestMultipleInputs(object): user2_sk, user2_vk = crypto.generate_key_pair() user3_sk, user3_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk, user2_vk]) + tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1181,7 +1044,7 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == expected_owned_inputs_user1 - tx = Transaction.transfer(tx.to_inputs(), [user3_vk], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user3_vk], 1)], tx.asset) tx = tx.sign([user_sk, user2_sk]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1197,7 +1060,7 @@ class TestMultipleInputs(object): user2_sk, user2_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1211,7 +1074,7 @@ class TestMultipleInputs(object): assert spent_inputs_user1 is None # create a transaction and block - tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset) tx = tx.sign([user_sk]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1228,7 +1091,7 @@ class TestMultipleInputs(object): # create a new users user2_sk, user2_vk = crypto.generate_key_pair() - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1246,7 +1109,7 @@ class TestMultipleInputs(object): assert spent_inputs_user1 is None # create a transaction and block - tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset) tx = tx.sign([user_sk]) block = b.create_block([tx]) b.write_block(block, durability='hard') @@ -1261,24 +1124,23 @@ class TestMultipleInputs(object): # Now there should be no spents (the block is invalid) assert spent_inputs_user1 is None - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' - 'same asset. Remove this after implementing ', - 'multiple assets')) def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_vk): - import random from bigchaindb.common import crypto from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset # create a new users user2_sk, user2_vk = crypto.generate_key_pair() - transactions = [] - for i in range(3): - payload = {'somedata': random.randint(0, 255)} - tx = Transaction.create([b.me], [user_vk], payload) - tx = tx.sign([b.me_private]) - transactions.append(tx) - block = b.create_block(transactions) + # create a divisible asset with 3 outputs + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], + [([user_vk], 1), + ([user_vk], 1), + ([user_vk], 1)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + block = b.create_block([tx_create_signed]) b.write_block(block, durability='hard') owned_inputs_user1 = b.get_owned_ids(user_vk) @@ -1287,22 +1149,22 @@ class TestMultipleInputs(object): for input_tx in owned_inputs_user1: assert b.get_spent(input_tx.txid, input_tx.cid) is None - # select inputs to use - inputs = sum([tx.to_inputs() for tx in transactions[:2]], []) - - # create a transaction and block - tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]]) - tx = tx.sign([user_sk]) - block = b.create_block([tx]) + # transfer the first 2 inputs + tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2], + [([user2_vk], 1), ([user2_vk], 1)], + asset=tx_create.asset) + tx_transfer_signed = tx_transfer.sign([user_sk]) + block = b.create_block([tx_transfer_signed]) b.write_block(block, durability='hard') # check that used inputs are marked as spent - for ffill in inputs: - assert b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) == tx + for ffill in tx_create.to_inputs()[:2]: + spent_tx = b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) + assert spent_tx == tx_transfer_signed # check if remaining transaction that was unspent is also perceived # spendable by BigchainDB - assert b.get_spent(transactions[2].id, 0) is None + assert b.get_spent(tx_create.to_inputs()[2].tx_input.txid, 2) is None def test_get_spent_multiple_owners(self, b, user_sk, user_vk): import random @@ -1315,7 +1177,8 @@ class TestMultipleInputs(object): transactions = [] for i in range(3): payload = {'somedata': random.randint(0, 255)} - tx = Transaction.create([b.me], [user_vk, user2_vk], payload) + tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)], + payload) tx = tx.sign([b.me_private]) transactions.append(tx) block = b.create_block(transactions) @@ -1328,7 +1191,8 @@ class TestMultipleInputs(object): assert b.get_spent(input_tx.txid, input_tx.cid) is None # create a transaction - tx = Transaction.transfer(transactions[0].to_inputs(), [user3_vk], transactions[0].asset) + tx = Transaction.transfer(transactions[0].to_inputs(), + [([user3_vk], 1)], transactions[0].asset) tx = tx.sign([user_sk, user2_sk]) block = b.create_block([tx]) b.write_block(block, durability='hard') diff --git a/tests/pipelines/test_block_creation.py b/tests/pipelines/test_block_creation.py index 741d482a..171e3ca9 100644 --- a/tests/pipelines/test_block_creation.py +++ b/tests/pipelines/test_block_creation.py @@ -45,7 +45,7 @@ def test_create_block(b, user_vk): block_maker = BlockPipeline() for i in range(100): - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) block_maker.create(tx) @@ -63,7 +63,7 @@ def test_write_block(b, user_vk): txs = [] for i in range(100): - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) txs.append(tx) @@ -82,7 +82,7 @@ def test_duplicate_transaction(b, user_vk): txs = [] for i in range(10): - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) txs.append(tx) @@ -109,7 +109,7 @@ def test_delete_tx(b, user_vk): from bigchaindb.pipelines.block import BlockPipeline block_maker = BlockPipeline() for i in range(100): - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) block_maker.create(tx) # make sure the tx appears in the backlog @@ -138,7 +138,8 @@ def test_prefeed(b, user_vk): from bigchaindb.pipelines.block import initial for i in range(100): - tx = Transaction.create([b.me], [user_vk], {'msg': random.random()}) + tx = Transaction.create([b.me], [([user_vk], 1)], + {'msg': random.random()}) tx = tx.sign([b.me_private]) b.write_transaction(tx) @@ -167,7 +168,8 @@ def test_full_pipeline(b, user_vk): count_assigned_to_me = 0 for i in range(100): - tx = Transaction.create([b.me], [user_vk], {'msg': random.random()}) + tx = Transaction.create([b.me], [([user_vk], 1)], + {'msg': random.random()}) tx = tx.sign([b.me_private]).to_dict() assignee = random.choice([b.me, 'aaa', 'bbb', 'ccc']) tx['assignee'] = assignee diff --git a/tests/pipelines/test_election.py b/tests/pipelines/test_election.py index 669a75cb..d62869f5 100644 --- a/tests/pipelines/test_election.py +++ b/tests/pipelines/test_election.py @@ -15,7 +15,7 @@ def test_check_for_quorum_invalid(b, user_vk): e = election.Election() # create blocks with transactions - tx1 = Transaction.create([b.me], [user_vk]) + tx1 = Transaction.create([b.me], [([user_vk], 1)]) test_block = b.create_block([tx1]) # simulate a federation with four voters @@ -44,7 +44,7 @@ def test_check_for_quorum_invalid_prev_node(b, user_vk): e = election.Election() # create blocks with transactions - tx1 = Transaction.create([b.me], [user_vk]) + tx1 = Transaction.create([b.me], [([user_vk], 1)]) test_block = b.create_block([tx1]) # simulate a federation with four voters @@ -74,7 +74,7 @@ def test_check_for_quorum_valid(b, user_vk): e = election.Election() # create blocks with transactions - tx1 = Transaction.create([b.me], [user_vk]) + tx1 = Transaction.create([b.me], [([user_vk], 1)]) test_block = b.create_block([tx1]) # simulate a federation with four voters @@ -103,7 +103,7 @@ def test_check_requeue_transaction(b, user_vk): e = election.Election() # create blocks with transactions - tx1 = Transaction.create([b.me], [user_vk]) + tx1 = Transaction.create([b.me], [([user_vk], 1)]) test_block = b.create_block([tx1]) e.requeue_transactions(test_block) @@ -131,7 +131,8 @@ def test_full_pipeline(b, user_vk): # write two blocks txs = [] for i in range(100): - tx = Transaction.create([b.me], [user_vk], {'msg': random.random()}) + tx = Transaction.create([b.me], [([user_vk], 1)], + {'msg': random.random()}) tx = tx.sign([b.me_private]) txs.append(tx) @@ -140,7 +141,8 @@ def test_full_pipeline(b, user_vk): txs = [] for i in range(100): - tx = Transaction.create([b.me], [user_vk], {'msg': random.random()}) + tx = Transaction.create([b.me], [([user_vk], 1)], + {'msg': random.random()}) tx = tx.sign([b.me_private]) txs.append(tx) diff --git a/tests/pipelines/test_stale_monitor.py b/tests/pipelines/test_stale_monitor.py index 3a3e6ffe..95f298c5 100644 --- a/tests/pipelines/test_stale_monitor.py +++ b/tests/pipelines/test_stale_monitor.py @@ -10,7 +10,7 @@ import os def test_get_stale(b, user_vk): from bigchaindb.models import Transaction - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) b.write_transaction(tx, durability='hard') @@ -27,7 +27,7 @@ def test_get_stale(b, user_vk): def test_reassign_transactions(b, user_vk): from bigchaindb.models import Transaction # test with single node - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) b.write_transaction(tx, durability='hard') @@ -36,7 +36,7 @@ def test_reassign_transactions(b, user_vk): stm.reassign_transactions(tx.to_dict()) # test with federation - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) b.write_transaction(tx, durability='hard') @@ -51,7 +51,7 @@ def test_reassign_transactions(b, user_vk): assert reassigned_tx['assignee'] != tx['assignee'] # test with node not in federation - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]).to_dict() tx.update({'assignee': 'lol'}) tx.update({'assignment_timestamp': time.time()}) @@ -85,7 +85,7 @@ def test_full_pipeline(monkeypatch, user_vk): monkeypatch.setattr('time.time', lambda: 1) for i in range(100): - tx = Transaction.create([b.me], [user_vk]) + tx = Transaction.create([b.me], [([user_vk], 1)]) tx = tx.sign([b.me_private]) original_txc.append(tx.to_dict()) diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index 5bd0eb52..ec2f6204 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -8,7 +8,7 @@ from multipipes import Pipe, Pipeline def dummy_tx(b): from bigchaindb.models import Transaction - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) return tx @@ -130,7 +130,7 @@ def test_vote_validate_transaction(b): assert validation == (True, 123, 1) # NOTE: Submit unsigned transaction to `validate_tx` yields `False`. - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) validation = vote_obj.validate_tx(tx, 456, 10) assert validation == (False, 456, 10) @@ -224,7 +224,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch): # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() - tx = Transaction.create([b.me], [test_user_pub]) + tx = Transaction.create([b.me], [([test_user_pub], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) @@ -265,7 +265,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() - tx = Transaction.create([b.me], [test_user_pub]) + tx = Transaction.create([b.me], [([test_user_pub], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) @@ -274,7 +274,8 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): # create a `TRANSFER` transaction test_user2_priv, test_user2_pub = crypto.generate_key_pair() - tx2 = Transaction.transfer(tx.to_inputs(), [test_user2_pub], tx.asset) + tx2 = Transaction.transfer(tx.to_inputs(), [([test_user2_pub], 1)], + tx.asset) tx2 = tx2.sign([test_user_priv]) monkeypatch.setattr('time.time', lambda: 2) @@ -338,7 +339,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_vk): vote_pipeline.setup(indata=inpipe, outdata=outpipe) # NOTE: `tx` is invalid, because it wasn't signed. - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) block = b.create_block([tx]) inpipe.put(block.to_dict()) @@ -375,7 +376,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_vk): vote_pipeline.setup(indata=inpipe, outdata=outpipe) # NOTE: `tx` is invalid, because its id is not corresponding to its content - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]).to_dict() block['block']['transactions'][0]['id'] = 'an invalid tx id' @@ -414,7 +415,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_vk): vote_pipeline.setup(indata=inpipe, outdata=outpipe) # NOTE: `tx` is invalid, because its content is not corresponding to its id - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]).to_dict() block['block']['transactions'][0]['id'] = 'an invalid tx id' diff --git a/tests/test_models.py b/tests/test_models.py index 5033aebb..534052f9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -5,7 +5,7 @@ class TestTransactionModel(object): def test_validating_an_invalid_transaction(self, b): from bigchaindb.models import Transaction - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) tx.operation = 'something invalid' with raises(TypeError): @@ -41,7 +41,7 @@ class TestBlockModel(object): from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction - transactions = [Transaction.create([b.me], [b.me])] + transactions = [Transaction.create([b.me], [([b.me], 1)])] timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] expected_block = { @@ -73,7 +73,7 @@ class TestBlockModel(object): from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction - transactions = [Transaction.create([b.me], [b.me])] + transactions = [Transaction.create([b.me], [([b.me], 1)])] timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] expected = Block(transactions, b.me, timestamp, voters) @@ -113,7 +113,7 @@ class TestBlockModel(object): from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction - transactions = [Transaction.create([b.me], [b.me])] + transactions = [Transaction.create([b.me], [([b.me], 1)])] timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] @@ -136,7 +136,7 @@ class TestBlockModel(object): def test_compare_blocks(self, b): from bigchaindb.models import Block, Transaction - transactions = [Transaction.create([b.me], [b.me])] + transactions = [Transaction.create([b.me], [([b.me], 1)])] assert Block() != 'invalid comparison' assert Block(transactions) == Block(transactions) @@ -146,7 +146,7 @@ class TestBlockModel(object): from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction - transactions = [Transaction.create([b.me], [b.me])] + transactions = [Transaction.create([b.me], [([b.me], 1)])] timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] expected_block = { @@ -168,7 +168,7 @@ class TestBlockModel(object): from unittest.mock import Mock from bigchaindb.models import Transaction - tx = Transaction.create([b.me], [b.me]) + tx = Transaction.create([b.me], [([b.me], 1)]) block = b.create_block([tx]) has_previous_vote = Mock() diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index 00e40a37..7d382ca5 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -36,7 +36,7 @@ def test_post_create_transaction_endpoint(b, client): from bigchaindb.models import Transaction user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [user_pub]) + tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) @@ -48,7 +48,7 @@ def test_post_create_transaction_with_invalid_id(b, client): from bigchaindb.models import Transaction user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [user_pub]) + tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]).to_dict() tx['id'] = 'invalid id' @@ -60,7 +60,7 @@ def test_post_create_transaction_with_invalid_signature(b, client): from bigchaindb.models import Transaction user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [user_pub]) + tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]).to_dict() tx['transaction']['fulfillments'][0]['fulfillment'] = 'invalid signature' @@ -77,7 +77,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_vk, user_sk): input_valid = b.get_owned_ids(user_vk).pop() create_tx = b.get_transaction(input_valid.txid) - transfer_tx = Transaction.transfer(create_tx.to_inputs(), [user_pub], create_tx.asset) + transfer_tx = Transaction.transfer(create_tx.to_inputs(), + [([user_pub], 1)], create_tx.asset) transfer_tx = transfer_tx.sign([user_sk]) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) @@ -94,7 +95,8 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_vk, user_ input_valid = b.get_owned_ids(user_vk).pop() create_tx = b.get_transaction(input_valid.txid) - transfer_tx = Transaction.transfer(create_tx.to_inputs(), [user_pub], create_tx.asset) + transfer_tx = Transaction.transfer(create_tx.to_inputs(), + [([user_pub], 1)], create_tx.asset) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) assert res.status_code == 400