diff --git a/bigchaindb/models.py b/bigchaindb/models.py index 159e9f49..c6e81956 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -88,6 +88,11 @@ class Transaction(Transaction): if output.amount < 1: raise AmountError('`amount` needs to be greater than zero') + # Validate that all inputs are distinct + links = [i.fulfills.to_uri() for i in self.inputs] + if len(links) != len(set(links)): + raise DoubleSpend('tx "{}" spends inputs twice'.format(self.id)) + # validate asset id asset_id = Transaction.get_asset_id(input_txs) if asset_id != self.asset['id']: diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 8a2040e8..4d9314a1 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -1192,3 +1192,33 @@ def test_get_outputs_filtered(): get_outputs.assert_called_once_with('abc') get_spent.assert_not_called() assert out == get_outputs.return_value + + +@pytest.mark.bdb +def test_cant_spend_same_input_twice_in_tx(b, genesis_block): + """ + Recreate duplicated fulfillments bug + https://github.com/bigchaindb/bigchaindb/issues/1099 + """ + from bigchaindb.models import Transaction + from bigchaindb.common.exceptions import DoubleSpend + + # create a divisible asset + tx_create = Transaction.create([b.me], [([b.me], 100)]) + tx_create_signed = tx_create.sign([b.me_private]) + assert b.validate_transaction(tx_create_signed) == tx_create_signed + + # create a block and valid vote + block = b.create_block([tx_create_signed]) + b.write_block(block) + vote = b.vote(block.id, genesis_block.id, True) + b.write_vote(vote) + + # Create a transfer transaction with duplicated fulfillments + dup_inputs = tx_create.to_inputs() + tx_create.to_inputs() + tx_transfer = Transaction.transfer(dup_inputs, [([b.me], 200)], + asset_id=tx_create.id) + tx_transfer_signed = tx_transfer.sign([b.me_private]) + assert b.is_valid_transaction(tx_transfer_signed) is False + with pytest.raises(DoubleSpend): + tx_transfer_signed.validate(b)