diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 5e67dbea..39236c3d 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -16,9 +16,14 @@ class Bigchain(object): Create, read, sign, write transactions to the database """ + # return if a block has been voted invalid BLOCK_INVALID = 'invalid' - BLOCK_VALID = 'valid' - BLOCK_UNDECIDED = 'undecided' + # return if a block is valid, or tx is in valid block + BLOCK_VALID = TX_VALID = 'valid' + # return if block is undecided, or tx is in undecided block + BLOCK_UNDECIDED = TX_UNDECIDED = 'undecided' + # return if transaction is in backlog + TX_IN_BACKLOG = 'backlog' def __init__(self, host=None, port=None, dbname=None, public_key=None, private_key=None, keyring=[], @@ -132,46 +137,57 @@ class Bigchain(object): response = r.table('backlog').insert(signed_transaction, durability=durability).run(self.conn) return response - def get_transaction(self, txid): + def get_transaction(self, txid, include_status=False): """Retrieve a transaction with `txid` from bigchain. - Queries the bigchain for a transaction that was already included in a block. + Queries the bigchain for a transaction, if it's in a valid or invalid + block. Args: txid (str): transaction id of the transaction to query + include_status (bool): also return the status of the transaction + the return value is then a tuple: (tx, status) Returns: A dict with the transaction details if the transaction was found. - - If no transaction with that `txid` was found it returns `None` + Will add the transaction status to payload ('valid', 'undecided', + or 'backlog'). If no transaction with that `txid` was found it + returns `None` """ + response, tx_status = None, None + validity = self.get_blocks_status_containing_tx(txid) if validity: # Disregard invalid blocks, and return if there are no valid or undecided blocks validity = {_id: status for _id, status in validity.items() if status != Bigchain.BLOCK_INVALID} - if not validity: - return None + if validity: - # If the transaction is in a valid or any undecided block, return it. Does not check - # if transactions in undecided blocks are consistent, but selects the valid block before - # undecided ones - for _id in validity: - target_block_id = _id - if validity[_id] == Bigchain.BLOCK_VALID: - break + tx_status = self.TX_UNDECIDED + # If the transaction is in a valid or any undecided block, return it. Does not check + # if transactions in undecided blocks are consistent, but selects the valid block before + # undecided ones + for target_block_id in validity: + if validity[target_block_id] == Bigchain.BLOCK_VALID: + tx_status = self.TX_VALID + break - # Query the transaction in the target block and return - response = r.table('bigchain', read_mode=self.read_mode).get(target_block_id)\ - .get_field('block').get_field('transactions')\ - .filter(lambda tx: tx['id'] == txid).run(self.conn) - - return response[0] + # Query the transaction in the target block and return + response = r.table('bigchain', read_mode=self.read_mode).get(target_block_id)\ + .get_field('block').get_field('transactions')\ + .filter(lambda tx: tx['id'] == txid).run(self.conn)[0] else: - return None + # Otherwise, check the backlog + response = r.table('backlog').get(txid).run(self.conn) + if response: + tx_status = self.TX_IN_BACKLOG + if include_status: + return response, tx_status + else: + return response def search_block_election_on_index(self, value, index): """Retrieve block election information given a secondary index and value diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index b00828e8..25df9a02 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -110,25 +110,37 @@ class TestBigchainApi(object): assert response['inserted'] == 1 @pytest.mark.usefixtures('inputs') - def test_read_transaction(self, b, user_vk, user_sk): + def test_read_transaction_undecided_block(self, b, user_vk, user_sk): input_tx = b.get_owned_ids(user_vk).pop() tx = b.create_transaction(user_vk, user_vk, input_tx, 'TRANSFER') tx_signed = b.sign_transaction(tx, user_sk) - b.write_transaction(tx_signed) # create block and write it to the bighcain before retrieving the transaction block = b.create_block([tx_signed]) b.write_block(block, durability='hard') - response = b.get_transaction(tx_signed["id"]) + response, status = b.get_transaction(tx_signed["id"], include_status=True) + # add validity information, which will be returned assert util.serialize(tx_signed) == util.serialize(response) + assert status == b.TX_UNDECIDED + + @pytest.mark.usefixtures('inputs') + def test_read_transaction_backlog(self, b, user_vk, user_sk): + input_tx = b.get_owned_ids(user_vk).pop() + tx = b.create_transaction(user_vk, user_vk, input_tx, 'TRANSFER') + tx_signed = b.sign_transaction(tx, user_sk) + b.write_transaction(tx_signed) + + response, status = b.get_transaction(tx_signed["id"], include_status=True) + # add validity information, which will be returned + assert util.serialize(tx_signed) == util.serialize(response) + assert status == b.TX_IN_BACKLOG @pytest.mark.usefixtures('inputs') def test_read_transaction_invalid_block(self, b, user_vk, user_sk): input_tx = b.get_owned_ids(user_vk).pop() tx = b.create_transaction(user_vk, user_vk, input_tx, 'TRANSFER') tx_signed = b.sign_transaction(tx, user_sk) - b.write_transaction(tx_signed) # create block block = b.create_block([tx_signed]) @@ -142,6 +154,26 @@ class TestBigchainApi(object): # should be None, because invalid blocks are ignored assert response is None + @pytest.mark.usefixtures('inputs') + def test_read_transaction_valid_block(self, b, user_vk, user_sk): + input_tx = b.get_owned_ids(user_vk).pop() + tx = b.create_transaction(user_vk, user_vk, input_tx, 'TRANSFER') + tx_signed = b.sign_transaction(tx, user_sk) + b.write_transaction(tx_signed) + + # create block + block = b.create_block([tx_signed]) + b.write_block(block, durability='hard') + + # vote the block invalid + vote = b.vote(block['id'], b.get_last_voted_block()['id'], True) + b.write_vote(vote) + + response, status = b.get_transaction(tx_signed["id"], include_status=True) + # add validity information, which will be returned + assert util.serialize(tx_signed) == util.serialize(response) + assert status == b.TX_VALID + @pytest.mark.usefixtures('inputs') def test_assign_transaction_one_node(self, b, user_vk, user_sk): input_tx = b.get_owned_ids(user_vk).pop()