1
0
mirror of https://github.com/bigchaindb/bigchaindb.git synced 2024-06-28 00:27:45 +02:00

Added validation for amounts

Added a new db call to return an asset instance given the id

Created tests
This commit is contained in:
Rodolphe Marques 2016-11-06 20:00:47 +01:00
parent db55aa8153
commit a212aba35b
4 changed files with 114 additions and 7 deletions

View File

@ -6,7 +6,7 @@ from time import time
from itertools import compress
from bigchaindb.common import crypto, exceptions
from bigchaindb.common.util import gen_timestamp, serialize
from bigchaindb.common.transaction import TransactionLink, Metadata
from bigchaindb.common.transaction import TransactionLink, Metadata, Asset
import rethinkdb as r
@ -366,6 +366,32 @@ class Bigchain(object):
return [Transaction.from_dict(tx) for tx in cursor]
def get_asset_by_id(self, asset_id):
"""Returns the asset associated with an asset_id
Args:
asset_id (str): The asset id
Returns:
:class:`~bigchaindb.common.transaction.Asset` if the asset
exists else None
"""
cursor = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(asset_id, index='asset_id')
.concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction:
transaction['transaction']['asset']['id'] == asset_id)
.filter(lambda transaction:
transaction['transaction']['operation'] == 'CREATE')
.pluck({'transaction': 'asset'}))
cursor = list(cursor)
if cursor:
return Asset.from_dict(cursor[0]['transaction']['asset'])
return cursor
def get_spent(self, txid, cid):
"""Check if a `txid` was already used as an input.

View File

@ -3,7 +3,7 @@ from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
OperationError, DoubleSpend,
TransactionDoesNotExist,
FulfillmentNotInValidBlock,
AssetIdMismatch)
AssetIdMismatch, AmountError)
from bigchaindb.common.transaction import Transaction, Asset
from bigchaindb.common.util import gen_timestamp, serialize
@ -41,7 +41,8 @@ class Transaction(Transaction):
if inputs_defined:
raise ValueError('A CREATE operation has no inputs')
# validate asset
self.asset._validate_asset()
amount = sum([condition.amount for condition in self.conditions])
self.asset._validate_asset(amount=amount)
elif self.operation == Transaction.TRANSFER:
if not inputs_defined:
raise ValueError('Only `CREATE` transactions can have null '
@ -49,6 +50,7 @@ class Transaction(Transaction):
# check inputs
# store the inputs so that we can check if the asset ids match
input_txs = []
input_amount = 0
for ffill in self.fulfillments:
input_txid = ffill.tx_input.txid
input_cid = ffill.tx_input.cid
@ -71,11 +73,28 @@ class Transaction(Transaction):
input_conditions.append(input_tx.conditions[input_cid])
input_txs.append(input_tx)
input_amount += input_tx.conditions[input_cid].amount
# validate asset id
asset_id = Asset.get_asset_id(input_txs)
if asset_id != self.asset.data_id:
raise AssetIdMismatch('The asset id of the input does not match the asset id of the transaction')
raise AssetIdMismatch(('The asset id of the input does not'
' match the asset id of the'
' transaction'))
# get the asset creation to see if its divisible or not
asset = bigchain.get_asset_by_id(asset_id)
# validate the asset
asset._validate_asset(amount=input_amount)
# validate the amounts
output_amount = sum([condition.amount for
condition in self.conditions])
if output_amount != input_amount:
raise AmountError(('The amout used in the inputs `{}`'
' needs to be same as the amount used'
' in the outputs `{}`')
.format(input_amount, output_amount))
else:
allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
raise TypeError('`operation`: `{}` must be either {}.'

View File

@ -146,7 +146,7 @@ def test_get_txs_by_asset_id(b, user_vk, user_sk):
assert txs[0].asset.data_id == asset_id
# create a transfer transaction
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_vk],
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)],
tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
# create the block
@ -165,6 +165,31 @@ def test_get_txs_by_asset_id(b, user_vk, user_sk):
assert asset_id == txs[1].asset.data_id
@pytest.mark.usefixtures('inputs')
def test_get_asset_by_id(b, user_vk, user_sk):
from bigchaindb.models import Transaction
tx_create = b.get_owned_ids(user_vk).pop()
tx_create = b.get_transaction(tx_create.txid)
asset_id = tx_create.asset.data_id
# create a transfer transaction
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)],
tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
# create the block
block = b.create_block([tx_transfer_signed])
b.write_block(block, durability='hard')
# vote the block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
txs = b.get_txs_by_asset_id(asset_id)
assert len(txs) == 2
asset = b.get_asset_by_id(asset_id)
assert asset == tx_create.asset
def test_create_invalid_divisible_asset(b, user_vk, user_sk):
from bigchaindb.models import Transaction, Asset
from bigchaindb.common.exceptions import AmountError

View File

@ -546,6 +546,45 @@ def test_multiple_in_different_transactions(b, user_vk, user_sk):
assert fid1_input == tx_transfer1.id
# In a TRANSFER transaction of a divisible asset the amount being spent in the
# inputs needs to match the amount being sent in the outputs.
# In other words `amount_in_inputs - amount_in_outputs == 0`
@pytest.mark.usefixtures('inputs')
def test_amount_error_transfer(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
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me], [([user_vk], 100)], 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)
# TRANSFER
# output amount less than input amount
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
with pytest.raises(AmountError):
tx_transfer_signed.validate(b)
# TRANSFER
# output amount greater than input amount
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 101)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
with pytest.raises(AmountError):
tx_transfer_signed.validate(b)
@pytest.mark.skip
@pytest.mark.usefixtures('inputs')
def test_transaction_unfulfilled_fulfillments(b, user_vk,
user_sk):
@ -576,8 +615,6 @@ def test_transaction_unfulfilled_fulfillments(b, user_vk,
# invalid. Somehow the validation passes
assert b.is_valid_transaction(tx_transfer_signed) == False
#test input output amount mismatch. Both when output is less and greater then input
@pytest.mark.skip(reason=('get_subcondition_from_vk does not always work'
' as expected'))