diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 20b03243..5a007eab 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -189,19 +189,25 @@ class Bigchain(object): return False def get_transaction(self, txid, include_status=False): - """Retrieve a transaction with `txid` from bigchain. + """Get the transaction with the specified `txid` (and optionally its status) - Queries the bigchain for a transaction, if it's in a valid or invalid - block. + This query begins by looking in the bigchain table for all blocks containing + a transaction with the specified `txid`. If one of those blocks is valid, it + returns the matching transaction from that block. Else if some of those + blocks are undecided, it returns a matching transaction from one of them. If + the transaction was found in invalid blocks only, or in no blocks, then this + query looks for a matching transaction in the backlog table, and if it finds + one there, it returns that. Args: - txid (str): transaction id of the transaction to query + txid (str): transaction id of the transaction to get include_status (bool): also return the status of the transaction the return value is then a tuple: (tx, status) Returns: A :class:`~.models.Transaction` instance if the transaction - was found, otherwise ``None``. + was found in a valid block, an undecided block, or the backlog table, + otherwise ``None``. If :attr:`include_status` is ``True``, also returns the transaction's status if the transaction was found. """ @@ -209,6 +215,7 @@ class Bigchain(object): response, tx_status = None, None validity = self.get_blocks_status_containing_tx(txid) + check_backlog = True if validity: # Disregard invalid blocks, and return if there are no valid or undecided blocks @@ -216,10 +223,14 @@ class Bigchain(object): if status != Bigchain.BLOCK_INVALID} if validity: + # The transaction _was_ found in an undecided or valid block, + # so there's no need to look in the backlog table + check_backlog = False + 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 + # 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 @@ -228,8 +239,7 @@ class Bigchain(object): # Query the transaction in the target block and return response = self.backend.get_transaction_from_block(txid, target_block_id) - else: - # Otherwise, check the backlog + if check_backlog: response = self.backend.get_transaction_from_backlog(txid) if response: diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index f0a88c44..a6b76eb4 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -245,7 +245,7 @@ class TestBigchainApi(object): inputs = input_tx.to_inputs() tx = Transaction.transfer(inputs, [user_vk], input_tx.asset) tx = tx.sign([user_sk]) - b.write_transaction(tx) + # There's no need to b.write_transaction(tx) to the backlog # create block block = b.create_block([tx]) @@ -257,8 +257,37 @@ class TestBigchainApi(object): response = b.get_transaction(tx.id) # should be None, because invalid blocks are ignored + # and a copy of the tx is not in the backlog assert response is None + @pytest.mark.usefixtures('inputs') + def test_read_transaction_invalid_block_and_backlog(self, b, user_vk, user_sk): + from bigchaindb.models import Transaction + + 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 = tx.sign([user_sk]) + + # Make sure there's a copy of tx in the backlog + b.write_transaction(tx) + + # create block + block = b.create_block([tx]) + b.write_block(block, durability='hard') + + # vote the block invalid + vote = b.vote(block.id, b.get_last_voted_block().id, False) + b.write_vote(vote) + + # a copy of the tx is both in the backlog and in an invalid + # block, so get_transaction should return a transaction, + # and a status of TX_IN_BACKLOG + response, status = b.get_transaction(tx.id, include_status=True) + assert tx.to_dict() == response.to_dict() + assert status == b.TX_IN_BACKLOG + @pytest.mark.usefixtures('inputs') def test_genesis_block(self, b): import rethinkdb as r