diff --git a/bigchaindb/backend/mongodb/schema.py b/bigchaindb/backend/mongodb/schema.py index 527476f0..12b873e0 100644 --- a/bigchaindb/backend/mongodb/schema.py +++ b/bigchaindb/backend/mongodb/schema.py @@ -108,8 +108,8 @@ def create_votes_secondary_index(conn, dbname): def create_assets_secondary_index(conn, dbname): logger.info('Create `assets` secondary index.') - # is the first index redundant then? - # compound index to order votes by block id and node + # unique index on the id of the asset. + # the id is the txid of the transaction that created the asset conn.conn[dbname]['assets'].create_index('id', name='asset_id', unique=True) diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index 8f1325ef..5c37647c 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -213,13 +213,28 @@ def get_block(connection, block_id): @singledispatch def write_assets(connection, assets): - # TODO: write docstring + """Write a list of assets to the assets table. + + Args: + assets (list): a list of assets to write. + + Returns: + The database response. + """ raise NotImplementedError @singledispatch -def get_assets(connection, assets): - # TODO: write docstring +def get_assets(connection, asset_ids): + """Get a list of assets from the assets table. + + Args: + asset_ids (list): a of list of ids for the assets to be retrieved from + the database. + + Returns: + assets (list): the list of returned assets. + """ raise NotImplementedError diff --git a/bigchaindb/models.py b/bigchaindb/models.py index 81ca898c..53266930 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -315,13 +315,33 @@ class Block(object): @classmethod def from_db(cls, bigchain, block_dict): + """ + Helper method that reconstructs a block_dict that was returned from + the database. If checks what asset_ids to retrieve, retrieves the + assets from the assets table and reconstructs the block. + + Args: + bigchain (:class:`~bigchaindb.Bigchain`): An instance of Bigchain + used to perform database queries. + + Returns: + :class:`~Block` + + """ asset_ids = cls.get_asset_ids(block_dict) assets = bigchain.get_assets(asset_ids) block_dict = cls.couple_assets(block_dict, assets) return cls.from_dict(block_dict) def decouple_assets(self): - # TODO: Write documentation + """ + Extracts the assets from the `CREATE` transactions in the block. + + Returns: + tuple: (assets, block) with the assets being a list of dicts and + the block being the dict of the block with no assets in the CREATE + transactions. + """ block_dict = deepcopy(self.to_dict()) assets = [] for transaction in block_dict['block']['transactions']: @@ -335,7 +355,20 @@ class Block(object): @staticmethod def couple_assets(block_dict, assets): - # TODO: Write docstring + """ + Give a block_dict with not assets (as returned from a database call) + and a list of assets, reconstruct the original block by puting the + assets back into the `CREATE` transactions in the block. + + Args: + block_dict (:obj:`dict`): The block dict as returned from a + database call. + assets (:obj:`list` of :obj:`dict`): A list of assets returned from + a database call. + + Returns: + dict: The dict of the reconstructed block. + """ # create a dict with {'': asset} assets = {asset.pop('id'): asset for asset in assets} # add the assets to the block transactions @@ -348,7 +381,19 @@ class Block(object): @staticmethod def get_asset_ids(block_dict): - # TODO: Write docstring + """ + Given a block_dict return all the asset_ids for that block (the txid + of CREATE transactions). Usefull to know which assets to retrieve + from the database to reconstruct the block. + + Args: + block_dict (:obj:`dict`): The block dict as returned from a + database call. + + Returns: + list: The list of asset_ids in the block. + + """ asset_ids = [] for transaction in block_dict['block']['transactions']: if transaction['operation'] in [Transaction.CREATE, diff --git a/tests/backend/mongodb/test_queries.py b/tests/backend/mongodb/test_queries.py index 1363d9d7..c43c5fa4 100644 --- a/tests/backend/mongodb/test_queries.py +++ b/tests/backend/mongodb/test_queries.py @@ -1,4 +1,7 @@ +from copy import deepcopy + import pytest +import pymongo pytestmark = pytest.mark.bdb @@ -418,3 +421,46 @@ def test_get_txids_filtered(signed_create_tx, signed_transfer_tx): # Test get by asset and TRANSFER txids = set(query.get_txids_filtered(conn, asset_id, Transaction.TRANSFER)) assert txids == {signed_transfer_tx.id} + + +def test_write_assets(): + from bigchaindb.backend import connect, query + conn = connect() + + assets = [ + {'id': 1, 'data': '1'}, + {'id': 2, 'data': '2'}, + {'id': 3, 'data': '3'}, + # Duplicated id. Should not be written to the database + {'id': 1, 'data': '1'}, + ] + + # write the assets + query.write_assets(conn, deepcopy(assets)) + + # check that 3 assets were written to the database + cursor = conn.db.assets.find({}, projection={'_id': False})\ + .sort('id', pymongo.ASCENDING) + + assert cursor.count() == 3 + assert list(cursor) == assets[:-1] + + +def test_get_assets(): + from bigchaindb.backend import connect, query + conn = connect() + + assets = [ + {'id': 1, 'data': '1'}, + {'id': 2, 'data': '2'}, + {'id': 3, 'data': '3'}, + ] + + # write the assets + conn.db.assets.insert_many(deepcopy(assets), ordered=False) + + # read only 2 assets + cursor = query.get_assets(conn, [1, 3]) + + assert cursor.count() == 2 + assert list(cursor.sort('id', pymongo.ASCENDING)) == assets[::2] diff --git a/tests/test_block_model.py b/tests/test_block_model.py index 6e559cb2..981a64d7 100644 --- a/tests/test_block_model.py +++ b/tests/test_block_model.py @@ -153,3 +153,136 @@ class TestBlockModel(object): block = b.create_block([tx, tx]) with raises(DuplicateTransaction): block._validate_block(b) + + def test_decouple_assets(self, b): + from bigchaindb.models import Block, Transaction + + assets = [ + {'msg': '1'}, + {'msg': '2'}, + {'msg': '3'}, + ] + + txs = [] + # create 3 assets + for asset in assets: + tx = Transaction.create([b.me], [([b.me], 1)], asset=asset) + txs.append(tx) + + # create a `TRANSFER` transaction. + # the asset in `TRANSFER` transactions is not extracted + tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)], + asset_id=txs[0].id) + txs.append(tx) + + # create the block + block = Block(txs) + # decouple assets + assets_from_block, block_dict = block.decouple_assets() + + assert len(assets_from_block) == 3 + for i in range(3): + assert assets_from_block[i]['data'] == assets[i] + assert assets_from_block[i]['id'] == txs[i].id + + # check the `TRANSFER` transaction was not changed + assert block.transactions[3].to_dict() == \ + block_dict['block']['transactions'][3] + + def test_couple_assets(self, b): + from bigchaindb.models import Block, Transaction + + assets = [ + {'msg': '1'}, + {'msg': '2'}, + {'msg': '3'}, + ] + + txs = [] + # create 3 assets + for asset in assets: + tx = Transaction.create([b.me], [([b.me], 1)], asset=asset) + txs.append(tx) + + # create a `TRANSFER` transaction. + # the asset in `TRANSFER` transactions is not extracted + tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)], + asset_id=txs[0].id) + txs.append(tx) + + # create the block + block = Block(txs) + # decouple assets + assets_from_block, block_dict = block.decouple_assets() + + # reconstruct the block + block_dict_reconstructed = Block.couple_assets(block_dict, + assets_from_block) + + # check that the reconstructed block is the as the original block + assert block == Block.from_dict(block_dict_reconstructed) + + def test_get_asset_ids(self, b): + from bigchaindb.models import Block, Transaction + + assets = [ + {'msg': '1'}, + {'msg': '2'}, + {'msg': '3'}, + ] + + txs = [] + # create 3 assets + for asset in assets: + tx = Transaction.create([b.me], [([b.me], 1)], asset=asset) + txs.append(tx) + + # create a `TRANSFER` transaction. + # the asset in `TRANSFER` transactions is not extracted + tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)], + asset_id=txs[0].id) + txs.append(tx) + + # create the block + block = Block(txs) + # decouple assets + assets_from_block, block_dict = block.decouple_assets() + + # get the asset_ids and check that they are the same as the `CREATE` + # transactions + asset_ids = Block.get_asset_ids(block_dict) + assert asset_ids == [tx.id for tx in txs[:-1]] + + def test_from_db(self, b): + from bigchaindb.models import Block, Transaction + + assets = [ + {'msg': '1'}, + {'msg': '2'}, + {'msg': '3'}, + ] + + txs = [] + # create 3 assets + for asset in assets: + tx = Transaction.create([b.me], [([b.me], 1)], asset=asset) + txs.append(tx) + + # create a `TRANSFER` transaction. + # the asset in `TRANSFER` transactions is not extracted + tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)], + asset_id=txs[0].id) + txs.append(tx) + + # create the block + block = Block(txs) + # decouple assets + assets_from_block, block_dict = block.decouple_assets() + + # write the assets and block separatedly + b.write_assets(assets_from_block) + b.write_block(block) + + # check the reconstructed block is the same as the original block + block_from_db = Block.from_db(b, block_dict) + assert block == block_from_db