diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index 213eb3d3..849153af 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -299,6 +299,8 @@ class Condition(object): threshold = len(owners_after) if not isinstance(amount, int): raise TypeError('`amount` must be a int') + if amount < 1: + raise AmountError('`amount` needs to be greater than zero') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be an instance of list') if len(owners_after) == 0: diff --git a/bigchaindb/models.py b/bigchaindb/models.py index dc78ccfa..2d888af9 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -73,6 +73,8 @@ class Transaction(Transaction): input_conditions.append(input_tx.conditions[input_cid]) input_txs.append(input_tx) + if input_tx.conditions[input_cid].amount < 1: + raise AmountError('`amount` needs to be greater than zero') input_amount += input_tx.conditions[input_cid].amount # validate asset id @@ -87,8 +89,12 @@ class Transaction(Transaction): # validate the asset asset.validate_asset(amount=input_amount) # validate the amounts - output_amount = sum([condition.amount for - condition in self.conditions]) + output_amount = 0 + for condition in self.conditions: + if condition.amount < 1: + raise AmountError('`amount` needs to be greater than zero') + output_amount += condition.amount + if output_amount != input_amount: raise AmountError(('The amount used in the inputs `{}`' ' needs to be same as the amount used' diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py index 44181a4e..5ae360e0 100644 --- a/tests/assets/test_divisible_assets.py +++ b/tests/assets/test_divisible_assets.py @@ -1,5 +1,7 @@ import pytest +from unittest.mock import patch + from ..db.conftest import inputs # noqa @@ -684,3 +686,94 @@ def test_divide(b, user_vk, user_sk): assert len(tx_transfer_signed.conditions) == 3 for condition in tx_transfer_signed.conditions: assert condition.amount == 1 + + +# Check that negative inputs are caught when creating a TRANSFER transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_transfer(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + with pytest.raises(AmountError): + Transaction.transfer(tx_create.to_inputs(), + [([b.me], 4), ([b.me], -1)], + asset=tx_create.asset) + + +# Check that negative inputs are caught when validating a TRANSFER transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_transfer_validate(b, user_vk, user_sk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create_signed = tx_create.sign([b.me_private]) + # create block + block = b.create_block([tx_create_signed]) + assert block.validate(b) == block + b.write_block(block, durability='hard') + # vote + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + # create a transfer transaction with 3 outputs and check if the amount + # of each output is 1 + tx_transfer = Transaction.transfer(tx_create.to_inputs(), + [([b.me], 4), ([b.me], 1)], + asset=tx_create.asset) + tx_transfer.conditions[1].amount = -1 + tx_transfer_signed = tx_transfer.sign([user_sk]) + + with pytest.raises(AmountError): + tx_transfer_signed.validate(b) + + +# Check that negative inputs are caught when creating a CREATE transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_create(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + with pytest.raises(AmountError): + Transaction.create([b.me], [([user_vk], -3)], + asset=asset) + + +# Check that negative inputs are caught when validating a CREATE transaction +@pytest.mark.usefixtures('inputs') +def test_non_positive_amounts_on_create_validate(b, user_vk): + from bigchaindb.models import Transaction + from bigchaindb.common.transaction import Asset + from bigchaindb.common.exceptions import AmountError + + # CREATE divisible asset with 1 output with amount 3 + asset = Asset(divisible=True) + tx_create = Transaction.create([b.me], [([user_vk], 3)], + asset=asset) + tx_create.conditions[0].amount = -3 + with patch.object(Asset, 'validate_asset', return_value=None): + tx_create_signed = tx_create.sign([b.me_private]) + + with pytest.raises(AmountError): + tx_create_signed.validate(b)