From 003519b0a84effa8feffd18aad42408089cbad12 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Mon, 16 Jan 2017 15:03:54 +0100 Subject: [PATCH] duplicate tx.id into tx.asset.id in CREATE transactions --- bigchaindb/backend/mongodb/query.py | 20 +--------------- bigchaindb/common/schema/transaction.yaml | 4 ++-- bigchaindb/common/transaction.py | 16 ++++++++++--- tests/common/test_transaction.py | 28 ++++++++++++++++++++++- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/bigchaindb/backend/mongodb/query.py b/bigchaindb/backend/mongodb/query.py index 745ae731..7539d070 100644 --- a/bigchaindb/backend/mongodb/query.py +++ b/bigchaindb/backend/mongodb/query.py @@ -85,22 +85,6 @@ def get_blocks_status_from_transaction(conn, transaction_id): @register_query(MongoDBConnection) def get_txids_by_asset_id(conn, asset_id): - # get the txid of the create transaction for asset_id - cursor = conn.db['bigchain'].aggregate([ - {'$match': { - 'block.transactions.id': asset_id, - 'block.transactions.operation': 'CREATE' - }}, - {'$unwind': '$block.transactions'}, - {'$match': { - 'block.transactions.id': asset_id, - 'block.transactions.operation': 'CREATE' - }}, - {'$project': {'block.transactions.id': True}} - ]) - create_tx_txids = (elem['block']['transactions']['id'] for elem in cursor) - - # get txids of transfer transaction with asset_id cursor = conn.db['bigchain'].aggregate([ {'$match': { 'block.transactions.asset.id': asset_id @@ -111,9 +95,7 @@ def get_txids_by_asset_id(conn, asset_id): }}, {'$project': {'block.transactions.id': True}} ]) - transfer_tx_ids = (elem['block']['transactions']['id'] for elem in cursor) - - return chain(create_tx_txids, transfer_tx_ids) + return (elem['block']['transactions']['id'] for elem in cursor) @register_query(MongoDBConnection) diff --git a/bigchaindb/common/schema/transaction.yaml b/bigchaindb/common/schema/transaction.yaml index 86e5947b..3eea50ff 100644 --- a/bigchaindb/common/schema/transaction.yaml +++ b/bigchaindb/common/schema/transaction.yaml @@ -103,8 +103,8 @@ definitions: description: | Description of the asset being transacted. In the case of a ``TRANSFER`` transaction, this field contains only the ID of asset. In the case - of a ``CREATE`` transaction, this field contains only the user-defined - payload. + of a ``CREATE`` transaction, this field contains the user-defined + payload and may contain the asset ID (duplicated from the Transaction ID). additionalProperties: false properties: id: diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index f2180f0b..bda62663 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -444,6 +444,7 @@ class Transaction(object): asset is not None and not (isinstance(asset, dict) and 'data' in asset)): raise TypeError(('`asset` must be None or a dict holding a `data` ' " property instance for '{}' Transactions".format(operation))) + asset.pop('id', None) # Remove duplicated asset ID if there is one elif (operation == Transaction.TRANSFER and not (isinstance(asset, dict) and 'id' in asset)): raise TypeError(('`asset` must be a dict holding an `id` property ' @@ -926,9 +927,11 @@ class Transaction(object): tx_no_signatures = Transaction._remove_signatures(tx) tx_serialized = Transaction._to_str(tx_no_signatures) - tx_id = Transaction._to_hash(tx_serialized) - - tx['id'] = tx_id + tx['id'] = Transaction._to_hash(tx_serialized) + if self.operation == Transaction.CREATE: + # Duplicate asset into asset for consistency with TRANSFER + # transactions + tx['asset']['id'] = tx['id'] return tx @staticmethod @@ -952,6 +955,9 @@ class Transaction(object): # case could yield incorrect signatures. This is why we only # set it to `None` if it's set in the dict. input_['fulfillment'] = None + # Pop duplicated asset_id from CREATE tx + if tx_dict['operation'] == Transaction.CREATE: + tx_dict['asset'].pop('id', None) return tx_dict @staticmethod @@ -1031,6 +1037,10 @@ class Transaction(object): "the hash of its body, i.e. it's not valid.") raise InvalidHash(err_msg.format(proposed_tx_id)) + if tx_body.get('operation') == Transaction.CREATE: + if proposed_tx_id != tx_body['asset'].get('id'): + raise InvalidHash("CREATE tx has wrong asset_id") + @classmethod def from_dict(cls, tx): """Transforms a Python dictionary to a Transaction object. diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index 0b8d6aac..e4417cbb 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -300,6 +300,7 @@ def test_transaction_serialization(user_input, user_output, data): 'operation': Transaction.CREATE, 'metadata': None, 'asset': { + 'id': tx_id, 'data': data, } } @@ -307,7 +308,7 @@ def test_transaction_serialization(user_input, user_output, data): tx = Transaction(Transaction.CREATE, {'data': data}, [user_input], [user_output]) tx_dict = tx.to_dict() - tx_dict['id'] = tx_id + tx_dict['id'] = tx_dict['asset']['id'] = tx_id assert tx_dict == expected @@ -334,6 +335,7 @@ def test_transaction_deserialization(user_input, user_output, data): } tx_no_signatures = Transaction._remove_signatures(tx) tx['id'] = Transaction._to_hash(Transaction._to_str(tx_no_signatures)) + tx['asset']['id'] = tx['id'] tx = Transaction.from_dict(tx) assert tx == expected @@ -680,6 +682,7 @@ def test_create_create_transaction_single_io(user_output, user_pub, data): tx_dict = tx.to_dict() tx_dict['inputs'][0]['fulfillment'] = None tx_dict.pop('id') + tx_dict['asset'].pop('id') assert tx_dict == expected @@ -763,6 +766,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, metadata=data, asset=data) tx_dict = tx.to_dict() tx_dict.pop('id') + tx_dict['asset'].pop('id') tx_dict['inputs'][0]['fulfillment'] = None assert tx_dict == expected @@ -975,3 +979,25 @@ def test_validate_version(utx): utx.version = '1.0.0' with raises(SchemaValidationError): validate_transaction_model(utx) + + +def test_create_tx_has_asset_id(tx): + tx = tx.to_dict() + assert tx['id'] == tx['asset']['id'] + + +def test_create_tx_validates_asset_id(tx): + from bigchaindb.common.transaction import Transaction + from bigchaindb.common.exceptions import InvalidHash + + tx = tx.to_dict() + + # Test fails with wrong asset_id + tx['asset']['id'] = tx['asset']['id'][::-1] + with raises(InvalidHash): + Transaction.from_dict(tx) + + # Test fails with no asset_id + tx['asset'].pop('id') + with raises(InvalidHash): + Transaction.from_dict(tx)