mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00: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:
parent
db55aa8153
commit
a212aba35b
@ -6,7 +6,7 @@ from time import time
|
|||||||
from itertools import compress
|
from itertools import compress
|
||||||
from bigchaindb.common import crypto, exceptions
|
from bigchaindb.common import crypto, exceptions
|
||||||
from bigchaindb.common.util import gen_timestamp, serialize
|
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
|
import rethinkdb as r
|
||||||
|
|
||||||
@ -366,6 +366,32 @@ class Bigchain(object):
|
|||||||
|
|
||||||
return [Transaction.from_dict(tx) for tx in cursor]
|
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):
|
def get_spent(self, txid, cid):
|
||||||
"""Check if a `txid` was already used as an input.
|
"""Check if a `txid` was already used as an input.
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
|
|||||||
OperationError, DoubleSpend,
|
OperationError, DoubleSpend,
|
||||||
TransactionDoesNotExist,
|
TransactionDoesNotExist,
|
||||||
FulfillmentNotInValidBlock,
|
FulfillmentNotInValidBlock,
|
||||||
AssetIdMismatch)
|
AssetIdMismatch, AmountError)
|
||||||
from bigchaindb.common.transaction import Transaction, Asset
|
from bigchaindb.common.transaction import Transaction, Asset
|
||||||
from bigchaindb.common.util import gen_timestamp, serialize
|
from bigchaindb.common.util import gen_timestamp, serialize
|
||||||
|
|
||||||
@ -41,7 +41,8 @@ class Transaction(Transaction):
|
|||||||
if inputs_defined:
|
if inputs_defined:
|
||||||
raise ValueError('A CREATE operation has no inputs')
|
raise ValueError('A CREATE operation has no inputs')
|
||||||
# validate asset
|
# 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:
|
elif self.operation == Transaction.TRANSFER:
|
||||||
if not inputs_defined:
|
if not inputs_defined:
|
||||||
raise ValueError('Only `CREATE` transactions can have null '
|
raise ValueError('Only `CREATE` transactions can have null '
|
||||||
@ -49,6 +50,7 @@ class Transaction(Transaction):
|
|||||||
# check inputs
|
# check inputs
|
||||||
# store the inputs so that we can check if the asset ids match
|
# store the inputs so that we can check if the asset ids match
|
||||||
input_txs = []
|
input_txs = []
|
||||||
|
input_amount = 0
|
||||||
for ffill in self.fulfillments:
|
for ffill in self.fulfillments:
|
||||||
input_txid = ffill.tx_input.txid
|
input_txid = ffill.tx_input.txid
|
||||||
input_cid = ffill.tx_input.cid
|
input_cid = ffill.tx_input.cid
|
||||||
@ -71,11 +73,28 @@ class Transaction(Transaction):
|
|||||||
|
|
||||||
input_conditions.append(input_tx.conditions[input_cid])
|
input_conditions.append(input_tx.conditions[input_cid])
|
||||||
input_txs.append(input_tx)
|
input_txs.append(input_tx)
|
||||||
|
input_amount += input_tx.conditions[input_cid].amount
|
||||||
|
|
||||||
# validate asset id
|
# validate asset id
|
||||||
asset_id = Asset.get_asset_id(input_txs)
|
asset_id = Asset.get_asset_id(input_txs)
|
||||||
if asset_id != self.asset.data_id:
|
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:
|
else:
|
||||||
allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
|
allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
|
||||||
raise TypeError('`operation`: `{}` must be either {}.'
|
raise TypeError('`operation`: `{}` must be either {}.'
|
||||||
|
@ -146,7 +146,7 @@ def test_get_txs_by_asset_id(b, user_vk, user_sk):
|
|||||||
assert txs[0].asset.data_id == asset_id
|
assert txs[0].asset.data_id == asset_id
|
||||||
|
|
||||||
# create a transfer transaction
|
# 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_create.asset)
|
||||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||||
# create the block
|
# 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
|
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):
|
def test_create_invalid_divisible_asset(b, user_vk, user_sk):
|
||||||
from bigchaindb.models import Transaction, Asset
|
from bigchaindb.models import Transaction, Asset
|
||||||
from bigchaindb.common.exceptions import AmountError
|
from bigchaindb.common.exceptions import AmountError
|
||||||
|
@ -546,6 +546,45 @@ def test_multiple_in_different_transactions(b, user_vk, user_sk):
|
|||||||
assert fid1_input == tx_transfer1.id
|
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')
|
@pytest.mark.usefixtures('inputs')
|
||||||
def test_transaction_unfulfilled_fulfillments(b, user_vk,
|
def test_transaction_unfulfilled_fulfillments(b, user_vk,
|
||||||
user_sk):
|
user_sk):
|
||||||
@ -576,8 +615,6 @@ def test_transaction_unfulfilled_fulfillments(b, user_vk,
|
|||||||
# invalid. Somehow the validation passes
|
# invalid. Somehow the validation passes
|
||||||
assert b.is_valid_transaction(tx_transfer_signed) == False
|
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'
|
@pytest.mark.skip(reason=('get_subcondition_from_vk does not always work'
|
||||||
' as expected'))
|
' as expected'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user