Merge pull request #794 from bigchaindb/divisible-assets

Divisible assets
This commit is contained in:
Sylvain Bellemare 2016-11-14 16:52:59 +01:00 committed by GitHub
commit ca2cf8d82a
18 changed files with 1421 additions and 654 deletions

View File

@ -3,13 +3,13 @@ from functools import reduce
from uuid import uuid4
from cryptoconditions import (Fulfillment as CCFulfillment,
ThresholdSha256Fulfillment, Ed25519Fulfillment,
PreimageSha256Fulfillment)
ThresholdSha256Fulfillment, Ed25519Fulfillment)
from cryptoconditions.exceptions import ParsingError
from bigchaindb.common.crypto import SigningKey, hash_data
from bigchaindb.common.exceptions import (KeypairMismatchException,
InvalidHash, InvalidSignature)
InvalidHash, InvalidSignature,
AmountError, AssetIdMismatch)
from bigchaindb.common.util import serialize, gen_timestamp
@ -96,6 +96,14 @@ class Fulfillment(object):
ffill['fid'] = fid
return ffill
@classmethod
def generate(cls, owners_before):
# TODO: write docstring
# The amount here does not really matter. It is only use on the
# condition data model but here we only care about the fulfillment
condition = Condition.generate(owners_before, 1)
return cls(condition.fulfillment, condition.owners_after)
@classmethod
def from_dict(cls, ffill):
"""Transforms a Python dictionary to a Fulfillment object.
@ -265,7 +273,7 @@ class Condition(object):
return cond
@classmethod
def generate(cls, owners_after):
def generate(cls, owners_after, amount):
"""Generates a Condition from a specifically formed tuple or list.
Note:
@ -275,34 +283,24 @@ class Condition(object):
[(address|condition)*, [(address|condition)*, ...], ...]
If however, the thresholds of individual threshold conditions
to be created have to be set specifically, a tuple of the
following structure is necessary:
([(address|condition)*,
([(address|condition)*, ...], subthreshold),
...], threshold)
Args:
owners_after (:obj:`list` of :obj:`str`|tuple): The users that
should be able to fulfill the Condition that is being
created.
owners_after (:obj:`list` of :obj:`str`): The public key of
the users that should be able to fulfill the Condition
that is being created.
amount (:obj:`int`): The amount locked by the condition.
Returns:
A Condition that can be used in a Transaction.
Returns:
Raises:
TypeError: If `owners_after` is not an instance of `list`.
TypeError: If `owners_after` is an empty list.
ValueError: If `owners_after` is an empty list.
"""
# TODO: We probably want to remove the tuple logic for weights here
# again:
# github.com/bigchaindb/bigchaindb/issues/730#issuecomment-255144756
if isinstance(owners_after, tuple):
owners_after, threshold = owners_after
else:
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:
@ -313,12 +311,12 @@ class Condition(object):
ffill = Ed25519Fulfillment(public_key=owners_after[0])
except TypeError:
ffill = owners_after[0]
return cls(ffill, owners_after)
return cls(ffill, owners_after, amount=amount)
else:
initial_cond = ThresholdSha256Fulfillment(threshold=threshold)
threshold_cond = reduce(cls._gen_condition, owners_after,
initial_cond)
return cls(threshold_cond, owners_after)
return cls(threshold_cond, owners_after, amount=amount)
@classmethod
def _gen_condition(cls, initial, current):
@ -338,9 +336,6 @@ class Condition(object):
Returns:
:class:`cryptoconditions.ThresholdSha256Fulfillment`:
"""
if isinstance(current, tuple):
owners_after, threshold = current
else:
owners_after = current
try:
threshold = len(owners_after)
@ -420,7 +415,7 @@ class Asset(object):
self.updatable = updatable
self.refillable = refillable
self._validate_asset()
self.validate_asset()
def __eq__(self, other):
try:
@ -463,7 +458,38 @@ class Asset(object):
"""Generates a unqiue uuid for an Asset"""
return str(uuid4())
def _validate_asset(self):
@staticmethod
def get_asset_id(transactions):
"""Get the asset id from a list of transaction ids.
This is useful when we want to check if the multiple inputs of a
transaction are related to the same asset id.
Args:
transactions (:obj:`list` of :class:`~bigchaindb.common.
transaction.Transaction`): list of transaction usually inputs
that should have a matching asset_id
Returns:
str: uuid of the asset.
Raises:
AssetIdMismatch: If the inputs are related to different assets.
"""
if not isinstance(transactions, list):
transactions = [transactions]
# create a set of asset_ids
asset_ids = {tx.asset.data_id for tx in transactions}
# check that all the transasctions have the same asset_id
if len(asset_ids) > 1:
raise AssetIdMismatch(('All inputs of all transactions passed'
' need to have the same asset id'))
return asset_ids.pop()
def validate_asset(self, amount=None):
"""Validates the asset"""
if self.data is not None and not isinstance(self.data, dict):
raise TypeError('`data` must be a dict instance or None')
@ -474,6 +500,77 @@ class Asset(object):
if not isinstance(self.updatable, bool):
raise TypeError('`updatable` must be a boolean')
if self.refillable:
raise NotImplementedError('Refillable assets are not yet'
' implemented')
if self.updatable:
raise NotImplementedError('Updatable assets are not yet'
' implemented')
# If the amount is supplied we can perform extra validations to
# the asset
if amount is not None:
if not isinstance(amount, int):
raise TypeError('`amount` must be an int')
if self.divisible is False and amount != 1:
raise AmountError('non divisible assets always have'
' amount equal to one')
# Since refillable assets are not yet implemented this should
# raise and exception
if self.divisible is True and amount < 2:
raise AmountError('divisible assets must have an amount'
' greater than one')
class AssetLink(Asset):
"""An object for unidirectional linking to a Asset.
"""
def __init__(self, data_id=None):
"""Used to point to a specific Asset.
Args:
data_id (str): A Asset to link to.
"""
self.data_id = data_id
def __bool__(self):
return self.data_id is not None
def __eq__(self, other):
return isinstance(other, AssetLink) and \
self.to_dict() == self.to_dict()
@classmethod
def from_dict(cls, link):
"""Transforms a Python dictionary to a AssetLink object.
Args:
link (dict): The link to be transformed.
Returns:
:class:`~bigchaindb.common.transaction.AssetLink`
"""
try:
return cls(link['id'])
except TypeError:
return cls()
def to_dict(self):
"""Transforms the object to a Python dictionary.
Returns:
(dict|None): The link as an alternative serialization format.
"""
if self.data_id is None:
return None
else:
return {
'id': self.data_id
}
class Metadata(object):
"""Metadata is used to store a dictionary and its hash in a Transaction."""
@ -612,9 +709,17 @@ class Transaction(object):
self.fulfillments = fulfillments if fulfillments else []
self.metadata = metadata
# validate asset
# we know that each transaction relates to a single asset
# we can sum the amount of all the conditions
# for transactions other then CREATE we only have an id so there is
# nothing we can validate
if self.operation == self.CREATE:
amount = sum([condition.amount for condition in self.conditions])
self.asset.validate_asset(amount=amount)
@classmethod
def create(cls, owners_before, owners_after, metadata=None, asset=None,
secret=None, time_expire=None):
def create(cls, owners_before, owners_after, metadata=None, asset=None):
"""A simple way to generate a `CREATE` transaction.
Note:
@ -622,7 +727,6 @@ class Transaction(object):
use cases:
- Ed25519
- ThresholdSha256
- PreimageSha256.
Additionally, it provides support for the following BigchainDB
use cases:
@ -637,10 +741,6 @@ class Transaction(object):
Transaction.
asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset
to be created in this Transaction.
secret (binarystr, optional): A secret string to create a hash-
lock Condition.
time_expire (int, optional): The UNIX time a Transaction is
valid.
Returns:
:class:`~bigchaindb.common.transaction.Transaction`
@ -649,54 +749,28 @@ class Transaction(object):
raise TypeError('`owners_before` must be a list instance')
if not isinstance(owners_after, list):
raise TypeError('`owners_after` must be a list instance')
if len(owners_before) == 0:
raise ValueError('`owners_before` list cannot be empty')
if len(owners_after) == 0:
raise ValueError('`owners_after` list cannot be empty')
metadata = Metadata(metadata)
if len(owners_before) == len(owners_after) and len(owners_after) == 1:
# NOTE: Standard case, one owner before, one after.
# NOTE: For this case its sufficient to use the same
# fulfillment for the fulfillment and condition.
ffill = Ed25519Fulfillment(public_key=owners_before[0])
ffill_tx = Fulfillment(ffill, owners_before)
cond_tx = Condition.generate(owners_after)
return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata)
fulfillments = []
conditions = []
elif len(owners_before) == len(owners_after) and len(owners_after) > 1:
raise NotImplementedError('Multiple inputs and outputs not'
'available for CREATE')
# NOTE: Multiple inputs and outputs case. Currently not supported.
ffills = [Fulfillment(Ed25519Fulfillment(public_key=owner_before),
[owner_before])
for owner_before in owners_before]
conds = [Condition.generate(owners) for owners in owners_after]
return cls(cls.CREATE, asset, ffills, conds, metadata)
# generate_conditions
for owner_after in owners_after:
if not isinstance(owner_after, tuple) or len(owner_after) != 2:
raise ValueError(('Each `owner_after` in the list must be a'
' tuple of `([<list of public keys>],'
' <amount>)`'))
pub_keys, amount = owner_after
conditions.append(Condition.generate(pub_keys, amount))
elif len(owners_before) == 1 and len(owners_after) > 1:
# NOTE: Multiple owners case
cond_tx = Condition.generate(owners_after)
ffill = Ed25519Fulfillment(public_key=owners_before[0])
ffill_tx = Fulfillment(ffill, owners_before)
return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata)
# generate fulfillments
fulfillments.append(Fulfillment.generate(owners_before))
elif (len(owners_before) == 1 and len(owners_after) == 0 and
secret is not None):
# NOTE: Hashlock condition case
hashlock = PreimageSha256Fulfillment(preimage=secret)
cond_tx = Condition(hashlock.condition_uri)
ffill = Ed25519Fulfillment(public_key=owners_before[0])
ffill_tx = Fulfillment(ffill, owners_before)
return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata)
elif (len(owners_before) > 0 and len(owners_after) == 0 and
time_expire is not None):
raise NotImplementedError('Timeout conditions will be implemented '
'later')
elif (len(owners_before) > 0 and len(owners_after) == 0 and
secret is None):
raise ValueError('Define a secret to create a hashlock condition')
else:
raise ValueError("These are not the cases you're looking for ;)")
return cls(cls.CREATE, asset, fulfillments, conditions, metadata)
@classmethod
def transfer(cls, inputs, owners_after, asset, metadata=None):
@ -743,17 +817,17 @@ class Transaction(object):
raise ValueError('`inputs` must contain at least one item')
if not isinstance(owners_after, list):
raise TypeError('`owners_after` must be a list instance')
if len(owners_after) == 0:
raise ValueError('`owners_after` list cannot be empty')
# NOTE: See doc strings `Note` for description.
if len(inputs) == len(owners_after):
if len(owners_after) == 1:
conditions = [Condition.generate(owners_after)]
elif len(owners_after) > 1:
conditions = [Condition.generate(owners) for owners
in owners_after]
else:
raise ValueError("`inputs` and `owners_after`'s count must be the "
"same")
conditions = []
for owner_after in owners_after:
if not isinstance(owner_after, tuple) or len(owner_after) != 2:
raise ValueError(('Each `owner_after` in the list must be a'
' tuple of `([<list of public keys>],'
' <amount>)`'))
pub_keys, amount = owner_after
conditions.append(Condition.generate(pub_keys, amount))
metadata = Metadata(metadata)
inputs = deepcopy(inputs)
@ -862,13 +936,12 @@ class Transaction(object):
key_pairs = {gen_public_key(SigningKey(private_key)):
SigningKey(private_key) for private_key in private_keys}
zippedIO = enumerate(zip(self.fulfillments, self.conditions))
for index, (fulfillment, condition) in zippedIO:
for index, fulfillment in enumerate(self.fulfillments):
# NOTE: We clone the current transaction but only add the condition
# and fulfillment we're currently working on plus all
# previously signed ones.
tx_partial = Transaction(self.operation, self.asset, [fulfillment],
[condition], self.metadata,
self.conditions, self.metadata,
self.timestamp, self.version)
tx_partial_dict = tx_partial.to_dict()
@ -1025,14 +1098,13 @@ class Transaction(object):
"""
input_condition_uris_count = len(input_condition_uris)
fulfillments_count = len(self.fulfillments)
conditions_count = len(self.conditions)
def gen_tx(fulfillment, condition, input_condition_uri=None):
"""Splits multiple IO Transactions into partial single IO
Transactions.
"""
tx = Transaction(self.operation, self.asset, [fulfillment],
[condition], self.metadata, self.timestamp,
self.conditions, self.metadata, self.timestamp,
self.version)
tx_dict = tx.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict)
@ -1043,11 +1115,10 @@ class Transaction(object):
tx_serialized,
input_condition_uri)
if not fulfillments_count == conditions_count == \
input_condition_uris_count:
raise ValueError('Fulfillments, conditions and '
if not fulfillments_count == input_condition_uris_count:
raise ValueError('Fulfillments and '
'input_condition_uris must have the same count')
else:
partial_transactions = map(gen_tx, self.fulfillments,
self.conditions, input_condition_uris)
return all(partial_transactions)
@ -1208,7 +1279,10 @@ class Transaction(object):
conditions = [Condition.from_dict(condition) for condition
in tx['conditions']]
metadata = Metadata.from_dict(tx['metadata'])
if tx['operation'] in [cls.CREATE, cls.GENESIS]:
asset = Asset.from_dict(tx['asset'])
else:
asset = AssetLink.from_dict(tx['asset'])
return cls(tx['operation'], asset, fulfillments, conditions,
metadata, tx['timestamp'], tx_body['version'])

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
from bigchaindb.common.transaction import TransactionLink, Asset
import bigchaindb
@ -182,10 +182,11 @@ class Bigchain(object):
try:
return self.validate_transaction(transaction)
except (ValueError, exceptions.OperationError, exceptions.TransactionDoesNotExist,
except (ValueError, exceptions.OperationError,
exceptions.TransactionDoesNotExist,
exceptions.TransactionOwnerError, exceptions.DoubleSpend,
exceptions.InvalidHash, exceptions.InvalidSignature,
exceptions.FulfillmentNotInValidBlock):
exceptions.FulfillmentNotInValidBlock, exceptions.AmountError):
return False
def get_block(self, block_id, include_status=False):
@ -365,6 +366,21 @@ class Bigchain(object):
cursor = self.backend.get_transactions_by_asset_id(asset_id)
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.backend.get_asset_by_id(asset_id)
cursor = list(cursor)
if cursor:
return Asset.from_dict(cursor[0]['transaction']['asset'])
def get_spent(self, txid, cid):
"""Check if a `txid` was already used as an input.
@ -533,7 +549,7 @@ class Bigchain(object):
"""Prepare a genesis block."""
metadata = {'message': 'Hello World from the BigchainDB'}
transaction = Transaction.create([self.me], [self.me],
transaction = Transaction.create([self.me], [([self.me], 1)],
metadata=metadata)
# NOTE: The transaction model doesn't expose an API to generate a

View File

@ -178,7 +178,27 @@ class RethinkDBBackend:
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']['asset']['id'] == asset_id))
def get_asset_by_id(self, asset_id):
"""Returns the asset associated with an asset_id.
Args:
asset_id (str): The asset id.
Returns:
Returns a rethinkdb cursor.
"""
return 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'}))
def get_spent(self, transaction_id, condition_id):
"""Check if a `txid` was already used as an input.

View File

@ -3,41 +3,11 @@ 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
class Asset(Asset):
@staticmethod
def get_asset_id(transactions):
"""Get the asset id from a list of transaction ids.
This is useful when we want to check if the multiple inputs of a transaction
are related to the same asset id.
Args:
transactions (list): list of transaction usually inputs that should have a matching asset_id
Returns:
str: uuid of the asset.
Raises:
AssetIdMismatch: If the inputs are related to different assets.
"""
if not isinstance(transactions, list):
transactions = [transactions]
# create a set of asset_ids
asset_ids = {tx.asset.data_id for tx in transactions}
# check that all the transasctions have the same asset_id
if len(asset_ids) > 1:
raise AssetIdMismatch("All inputs of a transaction need to have the same asset id.")
return asset_ids.pop()
class Transaction(Transaction):
def validate(self, bigchain):
"""Validate a transaction.
@ -71,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 '
@ -79,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
@ -101,11 +73,34 @@ 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
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 = 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'
' in the outputs `{}`')
.format(input_amount, output_amount))
else:
allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
raise TypeError('`operation`: `{}` must be either {}.'

View File

@ -40,10 +40,11 @@ class Vote:
self.validity = {}
self.invalid_dummy_tx = Transaction.create([self.bigchain.me],
[self.bigchain.me])
[([self.bigchain.me], 1)])
def validate_block(self, block):
if not self.bigchain.has_previous_vote(block['id'], block['block']['voters']):
if not self.bigchain.has_previous_vote(block['id'],
block['block']['voters']):
try:
block = Block.from_dict(block)
except (exceptions.InvalidHash, exceptions.InvalidSignature):

View File

@ -1,5 +1,7 @@
import pytest
from ..db.conftest import inputs
from unittest.mock import patch
from ..db.conftest import inputs # noqa
@pytest.mark.usefixtures('inputs')
@ -9,7 +11,7 @@ def test_asset_transfer(b, user_vk, user_sk):
tx_input = b.get_owned_ids(user_vk).pop()
tx_create = b.get_transaction(tx_input.txid)
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])
@ -18,61 +20,40 @@ def test_asset_transfer(b, user_vk, user_sk):
def test_validate_bad_asset_creation(b, user_vk):
from bigchaindb.models import Transaction
from bigchaindb.models import Transaction, Asset
# `divisible` needs to be a boolean
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx.asset.divisible = 1
with patch.object(Asset, 'validate_asset', return_value=None):
tx_signed = tx.sign([b.me_private])
with pytest.raises(TypeError):
tx_signed.validate(b)
# `refillable` needs to be a boolean
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx.asset.refillable = 1
with patch.object(Asset, 'validate_asset', return_value=None):
tx_signed = tx.sign([b.me_private])
with pytest.raises(TypeError):
b.validate_transaction(tx_signed)
# `updatable` needs to be a boolean
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx.asset.updatable = 1
with patch.object(Asset, 'validate_asset', return_value=None):
tx_signed = tx.sign([b.me_private])
with pytest.raises(TypeError):
b.validate_transaction(tx_signed)
# `data` needs to be a dictionary
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx.asset.data = 'a'
with patch.object(Asset, 'validate_asset', return_value=None):
tx_signed = tx.sign([b.me_private])
with pytest.raises(TypeError):
b.validate_transaction(tx_signed)
# TODO: Check where to test for the amount
"""
tx = b.create_transaction(b.me, user_vk, None, 'CREATE')
tx['transaction']['conditions'][0]['amount'] = 'a'
tx['id'] = get_hash_data(tx['transaction'])
tx_signed = b.sign_transaction(tx, b.me_private)
with pytest.raises(TypeError):
b.validate_transaction(tx_signed)
tx = b.create_transaction(b.me, user_vk, None, 'CREATE')
tx['transaction']['conditions'][0]['amount'] = 2
tx['transaction']['asset'].update({'divisible': False})
tx['id'] = get_hash_data(tx['transaction'])
tx_signed = b.sign_transaction(tx, b.me_private)
with pytest.raises(AmountError):
b.validate_transaction(tx_signed)
tx = b.create_transaction(b.me, user_vk, None, 'CREATE')
tx['transaction']['conditions'][0]['amount'] = 0
tx['id'] = get_hash_data(tx['transaction'])
tx_signed = b.sign_transaction(tx, b.me_private)
with pytest.raises(AmountError):
b.validate_transaction(tx_signed)
"""
@pytest.mark.usefixtures('inputs')
def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk):
@ -81,7 +62,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk):
tx_create = b.get_owned_ids(user_vk).pop()
tx_create = b.get_transaction(tx_create.txid)
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.asset.data_id = 'aaa'
tx_transfer_signed = tx_transfer.sign([user_sk])
@ -92,7 +73,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk):
def test_get_asset_id_create_transaction(b, user_vk):
from bigchaindb.models import Transaction, Asset
tx_create = Transaction.create([b.me], [user_vk])
tx_create = Transaction.create([b.me], [([user_vk], 1)])
asset_id = Asset.get_asset_id(tx_create)
assert asset_id == tx_create.asset.data_id
@ -105,7 +86,7 @@ def test_get_asset_id_transfer_transaction(b, user_vk, user_sk):
tx_create = b.get_owned_ids(user_vk).pop()
tx_create = b.get_transaction(tx_create.txid)
# 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 a block
@ -123,8 +104,8 @@ def test_asset_id_mismatch(b, user_vk):
from bigchaindb.models import Transaction, Asset
from bigchaindb.common.exceptions import AssetIdMismatch
tx1 = Transaction.create([b.me], [user_vk])
tx2 = Transaction.create([b.me], [user_vk])
tx1 = Transaction.create([b.me], [([user_vk], 1)])
tx2 = Transaction.create([b.me], [([user_vk], 1)])
with pytest.raises(AssetIdMismatch):
Asset.get_asset_id([tx1, tx2])
@ -144,7 +125,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
@ -161,3 +142,73 @@ def test_get_txs_by_asset_id(b, user_vk, user_sk):
assert tx_transfer.id in [t.id for t in txs]
assert asset_id == txs[0].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):
from bigchaindb.models import Transaction, Asset
from bigchaindb.common.exceptions import AmountError
# non divisible assets cannot have amount > 1
# Transaction.__init__ should raise an exception
asset = Asset(divisible=False)
with pytest.raises(AmountError):
Transaction.create([user_vk], [([user_vk], 2)], asset=asset)
# divisible assets need to have an amount > 1
# Transaction.__init__ should raise an exception
asset = Asset(divisible=True)
with pytest.raises(AmountError):
Transaction.create([user_vk], [([user_vk], 1)], asset=asset)
# even if a transaction is badly constructed the server should raise the
# exception
asset = Asset(divisible=False)
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction.create([user_vk], [([user_vk], 2)], asset=asset)
tx_signed = tx.sign([user_sk])
with pytest.raises(AmountError):
tx_signed.validate(b)
assert b.is_valid_transaction(tx_signed) is False
asset = Asset(divisible=True)
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction.create([user_vk], [([user_vk], 1)], asset=asset)
tx_signed = tx.sign([user_sk])
with pytest.raises(AmountError):
tx_signed.validate(b)
assert b.is_valid_transaction(tx_signed) is False
def test_create_valid_divisible_asset(b, user_vk, user_sk):
from bigchaindb.models import Transaction, Asset
asset = Asset(divisible=True)
tx = Transaction.create([user_vk], [([user_vk], 2)], asset=asset)
tx_signed = tx.sign([user_sk])
assert b.is_valid_transaction(tx_signed)

View File

@ -0,0 +1,779 @@
import pytest
from unittest.mock import patch
from ..db.conftest import inputs # noqa
# CREATE divisible asset
# Single input
# Single owners_before
# Single output
# Single owners_after
def test_single_in_single_own_single_out_single_own_create(b, user_vk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
asset = Asset(divisible=True)
tx = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1
assert tx_signed.conditions[0].amount == 100
assert len(tx_signed.fulfillments) == 1
# CREATE divisible asset
# Single input
# Single owners_before
# Multiple outputs
# Single owners_after per output
def test_single_in_single_own_multiple_out_single_own_create(b, user_vk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
asset = Asset(divisible=True)
tx = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)],
asset=asset)
tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 2
assert tx_signed.conditions[0].amount == 50
assert tx_signed.conditions[1].amount == 50
assert len(tx_signed.fulfillments) == 1
# CREATE divisible asset
# Single input
# Single owners_before
# Single output
# Multiple owners_after
def test_single_in_single_own_single_out_multiple_own_create(b, user_vk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
asset = Asset(divisible=True)
tx = Transaction.create([b.me], [([user_vk, user_vk], 100)], asset=asset)
tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1
assert tx_signed.conditions[0].amount == 100
condition = tx_signed.conditions[0].to_dict()
assert 'subfulfillments' in condition['condition']['details']
assert len(condition['condition']['details']['subfulfillments']) == 2
assert len(tx_signed.fulfillments) == 1
# CREATE divisible asset
# Single input
# Single owners_before
# Multiple outputs
# Mix: one output with a single owners_after, one output with multiple
# owners_after
def test_single_in_single_own_multiple_out_mix_own_create(b, user_vk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
asset = Asset(divisible=True)
tx = Transaction.create([b.me],
[([user_vk], 50), ([user_vk, user_vk], 50)],
asset=asset)
tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 2
assert tx_signed.conditions[0].amount == 50
assert tx_signed.conditions[1].amount == 50
condition_cid1 = tx_signed.conditions[1].to_dict()
assert 'subfulfillments' in condition_cid1['condition']['details']
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2
assert len(tx_signed.fulfillments) == 1
# CREATE divisible asset
# Single input
# Multiple owners_before
# Output combinations already tested above
def test_single_in_multiple_own_single_out_single_own_create(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
asset = Asset(divisible=True)
tx = Transaction.create([b.me, user_vk], [([user_vk], 100)], asset=asset)
tx_signed = tx.sign([b.me_private, user_sk])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1
assert tx_signed.conditions[0].amount == 100
assert len(tx_signed.fulfillments) == 1
ffill = tx_signed.fulfillments[0].fulfillment.to_dict()
assert 'subfulfillments' in ffill
assert len(ffill['subfulfillments']) == 2
# TRANSFER divisible asset
# Single input
# Single owners_before
# Single output
# Single owners_after
# TODO: I don't really need inputs. But I need the database to be setup or
# else there will be no genesis block and b.get_last_voted_block will
# fail.
# Is there a better way of doing this?
@pytest.mark.usefixtures('inputs')
def test_single_in_single_own_single_out_single_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# 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
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 1
# TRANSFER divisible asset
# Single input
# Single owners_before
# Multiple output
# Single owners_after
@pytest.mark.usefixtures('inputs')
def test_single_in_single_own_multiple_out_single_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# 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
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([b.me], 50), ([b.me], 50)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2
assert tx_transfer_signed.conditions[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50
assert len(tx_transfer_signed.fulfillments) == 1
# TRANSFER divisible asset
# Single input
# Single owners_before
# Single output
# Multiple owners_after
@pytest.mark.usefixtures('inputs')
def test_single_in_single_own_single_out_multiple_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# 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
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([b.me, b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
condition = tx_transfer_signed.conditions[0].to_dict()
assert 'subfulfillments' in condition['condition']['details']
assert len(condition['condition']['details']['subfulfillments']) == 2
assert len(tx_transfer_signed.fulfillments) == 1
# TRANSFER divisible asset
# Single input
# Single owners_before
# Multiple outputs
# Mix: one output with a single owners_after, one output with multiple
# owners_after
@pytest.mark.usefixtures('inputs')
def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# 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
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([b.me], 50), ([b.me, b.me], 50)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2
assert tx_transfer_signed.conditions[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50
condition_cid1 = tx_transfer_signed.conditions[1].to_dict()
assert 'subfulfillments' in condition_cid1['condition']['details']
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2
assert len(tx_transfer_signed.fulfillments) == 1
# TRANSFER divisible asset
# Single input
# Multiple owners_before
# Single output
# Single owners_after
@pytest.mark.usefixtures('inputs')
def test_single_in_multiple_own_single_out_single_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me], [([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
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 1
ffill = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
assert 'subfulfillments' in ffill
assert len(ffill['subfulfillments']) == 2
# TRANSFER divisible asset
# Multiple inputs
# Single owners_before per input
# Single output
# Single owners_after
@pytest.mark.usefixtures('inputs')
def test_multiple_in_single_own_single_out_single_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)],
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
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2
# TRANSFER divisible asset
# Multiple inputs
# Multiple owners_before per input
# Single output
# Single owners_after
@pytest.mark.usefixtures('inputs')
def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk, b.me], 50),
([user_vk, b.me], 50)],
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
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
assert 'subfulfillments' in ffill_fid0
assert 'subfulfillments' in ffill_fid1
assert len(ffill_fid0['subfulfillments']) == 2
assert len(ffill_fid1['subfulfillments']) == 2
# TRANSFER divisible asset
# Multiple inputs
# Mix: one input with a single owners_before, one input with multiple
# owners_before
# Single output
# Single owners_after
@pytest.mark.usefixtures('inputs')
def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 50),
([user_vk, b.me], 50)],
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
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
assert 'subfulfillments' not in ffill_fid0
assert 'subfulfillments' in ffill_fid1
assert len(ffill_fid1['subfulfillments']) == 2
# TRANSFER divisible asset
# Multiple inputs
# Mix: one input with a single owners_before, one input with multiple
# owners_before
# Multiple outputs
# Mix: one output with a single owners_after, one output with multiple
# owners_after
@pytest.mark.usefixtures('inputs')
def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_vk,
user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 50),
([user_vk, b.me], 50)],
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
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([b.me], 50), ([b.me, user_vk], 50)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2
assert tx_transfer_signed.conditions[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50
assert len(tx_transfer_signed.fulfillments) == 2
cond_cid0 = tx_transfer_signed.conditions[0].to_dict()
cond_cid1 = tx_transfer_signed.conditions[1].to_dict()
assert 'subfulfillments' not in cond_cid0['condition']['details']
assert 'subfulfillments' in cond_cid1['condition']['details']
assert len(cond_cid1['condition']['details']['subfulfillments']) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
assert 'subfulfillments' not in ffill_fid0
assert 'subfulfillments' in ffill_fid1
assert len(ffill_fid1['subfulfillments']) == 2
# TRANSFER divisible asset
# Multiple inputs from different transactions
# Single owners_before
# Single output
# Single owners_after
@pytest.mark.usefixtures('inputs')
def test_multiple_in_different_transactions(b, user_vk, user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
# `b` creates a divisible asset and assigns 50 shares to `b` and
# 50 shares to `user_vk`
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 50),
([b.me], 50)],
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 divisible asset
# `b` transfers its 50 shares to `user_vk`
# after this transaction `user_vk` will have a total of 100 shares
# split across two different transactions
tx_transfer1 = Transaction.transfer(tx_create.to_inputs([1]),
[([user_vk], 50)],
asset=tx_create.asset)
tx_transfer1_signed = tx_transfer1.sign([b.me_private])
# create block
block = b.create_block([tx_transfer1_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
# `user_vk` combines two different transaction with 50 shares each and
# transfers a total of 100 shares back to `b`
tx_transfer2 = Transaction.transfer(tx_create.to_inputs([0]) +
tx_transfer1.to_inputs([0]),
[([b.me], 100)],
asset=tx_create.asset)
tx_transfer2_signed = tx_transfer2.sign([user_sk])
assert tx_transfer2_signed.validate(b) == tx_transfer2_signed
assert len(tx_transfer2_signed.conditions) == 1
assert tx_transfer2_signed.conditions[0].amount == 100
assert len(tx_transfer2_signed.fulfillments) == 2
fid0_input = tx_transfer2_signed.fulfillments[0].to_dict()['input']['txid']
fid1_input = tx_transfer2_signed.fulfillments[1].to_dict()['input']['txid']
assert fid0_input == tx_create.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(reason='Figure out how to handle this case')
@pytest.mark.usefixtures('inputs')
def test_threshold_same_public_key(b, user_vk, user_sk):
# If we try to fulfill a threshold condition where each subcondition has
# the same key get_subcondition_from_vk will always return the first
# subcondition. This means that only the 1st subfulfillment will be
# generated
# Creating threshold conditions with the same key does not make sense but
# that does not mean that the code shouldn't work.
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me], [([user_vk, 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
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
@pytest.mark.usefixtures('inputs')
def test_sum_amount(b, user_vk, user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# CREATE divisible asset with 3 outputs with amount 1
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 1),
([user_vk], 1),
([user_vk], 1)],
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 one output and check if the amount
# is 3
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 3)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 3
@pytest.mark.usefixtures('inputs')
def test_divide(b, user_vk, user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# 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], 1), ([b.me], 1), ([b.me], 1)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
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)

View File

@ -22,6 +22,7 @@ def test_asset_creation_with_data(data):
def test_asset_invalid_asset_initialization():
from bigchaindb.common.transaction import Asset
# check types
with raises(TypeError):
Asset(data='some wrong type')
with raises(TypeError):
@ -31,6 +32,12 @@ def test_asset_invalid_asset_initialization():
with raises(TypeError):
Asset(updatable=1)
# check for features that are not yet implemented
with raises(NotImplementedError):
Asset(updatable=True)
with raises(NotImplementedError):
Asset(refillable=True)
def test_invalid_asset_comparison(data, data_id):
from bigchaindb.common.transaction import Asset
@ -69,12 +76,17 @@ def test_asset_deserialization(data, data_id):
def test_validate_asset():
from bigchaindb.common.transaction import Asset
from bigchaindb.common.exceptions import AmountError
# test amount errors
asset = Asset(divisible=False)
with raises(AmountError):
asset.validate_asset(amount=2)
asset = Asset(divisible=True)
with raises(AmountError):
asset.validate_asset(amount=1)
asset = Asset()
with raises(TypeError):
Asset(divisible=1)
with raises(TypeError):
Asset(refillable=1)
with raises(TypeError):
Asset(updatable=1)
with raises(TypeError):
Asset(data='we need more lemon pledge')
asset.validate_asset(amount='a')

View File

@ -1,4 +1,5 @@
from pytest import raises, mark
from pytest import raises
from unittest.mock import patch
def test_fulfillment_serialization(ffill_uri, user_pub):
@ -166,29 +167,7 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub,
expected_threshold.add_subfulfillment(expected_simple3)
expected.add_subfulfillment(expected_threshold)
cond = Condition.generate([user_pub, [user2_pub, expected_simple3]])
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_split_half_recursive_custom_threshold(user_pub,
user2_pub,
user3_pub):
from bigchaindb.common.transaction import Condition
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
expected_simple2 = Ed25519Fulfillment(public_key=user2_pub)
expected_simple3 = Ed25519Fulfillment(public_key=user3_pub)
expected = ThresholdSha256Fulfillment(threshold=1)
expected.add_subfulfillment(expected_simple1)
expected_threshold = ThresholdSha256Fulfillment(threshold=1)
expected_threshold.add_subfulfillment(expected_simple2)
expected_threshold.add_subfulfillment(expected_simple3)
expected.add_subfulfillment(expected_threshold)
cond = Condition.generate(([user_pub, ([user2_pub, expected_simple3], 1)],
1))
cond = Condition.generate([user_pub, [user2_pub, expected_simple3]], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
@ -208,7 +187,7 @@ def test_generate_conditions_split_half_single_owner(user_pub, user2_pub,
expected.add_subfulfillment(expected_threshold)
expected.add_subfulfillment(expected_simple1)
cond = Condition.generate([[expected_simple2, user3_pub], user_pub])
cond = Condition.generate([[expected_simple2, user3_pub], user_pub], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
@ -225,7 +204,7 @@ def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub):
expected.add_subfulfillment(expected_simple2)
expected.add_subfulfillment(expected_simple3)
cond = Condition.generate([user_pub, user2_pub, expected_simple3])
cond = Condition.generate([user_pub, user2_pub, expected_simple3], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
@ -234,7 +213,7 @@ def test_generate_conditions_single_owner(user_pub):
from cryptoconditions import Ed25519Fulfillment
expected = Ed25519Fulfillment(public_key=user_pub)
cond = Condition.generate([user_pub])
cond = Condition.generate([user_pub], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
@ -244,48 +223,23 @@ def test_generate_conditions_single_owner_with_condition(user_pub):
from cryptoconditions import Ed25519Fulfillment
expected = Ed25519Fulfillment(public_key=user_pub)
cond = Condition.generate([expected])
cond = Condition.generate([expected], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
# TODO FOR CC: see skip reason
@mark.skip(reason='threshold(hashlock).to_dict() exposes secret')
def test_generate_threshold_condition_with_hashlock(user_pub, user2_pub,
user3_pub):
from bigchaindb.common.transaction import Condition
from cryptoconditions import (PreimageSha256Fulfillment,
Ed25519Fulfillment,
ThresholdSha256Fulfillment)
secret = b'much secret, wow'
hashlock = PreimageSha256Fulfillment(preimage=secret)
expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
expected_simple3 = Ed25519Fulfillment(public_key=user3_pub)
expected = ThresholdSha256Fulfillment(threshold=2)
expected_sub = ThresholdSha256Fulfillment(threshold=2)
expected_sub.add_subfulfillment(expected_simple1)
expected_sub.add_subfulfillment(hashlock)
expected.add_subfulfillment(expected_simple3)
cond = Condition.generate([[user_pub, hashlock], expected_simple3])
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_invalid_parameters(user_pub, user2_pub,
user3_pub):
from bigchaindb.common.transaction import Condition
with raises(ValueError):
Condition.generate([])
Condition.generate([], 1)
with raises(TypeError):
Condition.generate('not a list')
Condition.generate('not a list', 1)
with raises(ValueError):
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]])
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
with raises(ValueError):
Condition.generate([[user_pub]])
Condition.generate([[user_pub]], 1)
def test_invalid_transaction_initialization():
@ -321,6 +275,7 @@ def test_invalid_transaction_initialization():
def test_create_default_asset_on_tx_initialization():
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, None)
expected = Asset()
asset = tx.asset
@ -510,9 +465,61 @@ def test_cast_transaction_link_to_boolean():
assert bool(TransactionLink(False, False)) is True
def test_asset_link_serialization():
from bigchaindb.common.transaction import AssetLink
data_id = 'a asset id'
expected = {
'id': data_id,
}
asset_link = AssetLink(data_id)
assert asset_link.to_dict() == expected
def test_asset_link_serialization_with_empty_payload():
from bigchaindb.common.transaction import AssetLink
expected = None
asset_link = AssetLink()
assert asset_link.to_dict() == expected
def test_asset_link_deserialization():
from bigchaindb.common.transaction import AssetLink
data_id = 'a asset id'
expected = AssetLink(data_id)
asset_link = {
'id': data_id
}
asset_link = AssetLink.from_dict(asset_link)
assert asset_link == expected
def test_asset_link_deserialization_with_empty_payload():
from bigchaindb.common.transaction import AssetLink
expected = AssetLink()
asset_link = AssetLink.from_dict(None)
assert asset_link == expected
def test_cast_asset_link_to_boolean():
from bigchaindb.common.transaction import AssetLink
assert bool(AssetLink()) is False
assert bool(AssetLink('a')) is True
assert bool(AssetLink(False)) is True
def test_add_fulfillment_to_tx(user_ffill):
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset(), [], [])
tx.add_fulfillment(user_ffill)
@ -522,6 +529,7 @@ def test_add_fulfillment_to_tx(user_ffill):
def test_add_fulfillment_to_tx_with_invalid_parameters():
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset())
with raises(TypeError):
tx.add_fulfillment('somewronginput')
@ -530,6 +538,7 @@ def test_add_fulfillment_to_tx_with_invalid_parameters():
def test_add_condition_to_tx(user_cond):
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset())
tx.add_condition(user_cond)
@ -539,6 +548,7 @@ def test_add_condition_to_tx(user_cond):
def test_add_condition_to_tx_with_invalid_parameters():
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset(), [], [])
with raises(TypeError):
tx.add_condition('somewronginput')
@ -614,16 +624,14 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv):
from bigchaindb.common.crypto import SigningKey
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction(Transaction.CREATE, Asset(),
tx = Transaction(Transaction.CREATE, Asset(divisible=True),
[user_ffill, deepcopy(user_ffill)],
[user_ffill, deepcopy(user_cond)])
[user_cond, deepcopy(user_cond)])
expected_first = deepcopy(tx)
expected_second = deepcopy(tx)
expected_first.fulfillments = [expected_first.fulfillments[0]]
expected_first.conditions = [expected_first.conditions[0]]
expected_second.fulfillments = [expected_second.fulfillments[1]]
expected_second.conditions = [expected_second.conditions[1]]
expected_first_bytes = str(expected_first).encode()
expected_first.fulfillments[0].fulfillment.sign(expected_first_bytes,
@ -674,7 +682,7 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond,
Fulfillment, Condition, Asset)
from cryptoconditions import Ed25519Fulfillment
tx = Transaction(Transaction.CREATE, Asset(),
tx = Transaction(Transaction.CREATE, Asset(divisible=True),
[user_ffill, deepcopy(user_ffill)],
[user_cond, deepcopy(user_cond)])
tx.sign([user_priv])
@ -715,10 +723,6 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx,
with raises(TypeError):
transfer_tx.operation = "Operation that doesn't exist"
transfer_tx.fulfillments_valid([utx.conditions[0]])
with raises(ValueError):
tx = utx.sign([user_priv])
tx.conditions = []
tx.fulfillments_valid()
def test_create_create_transaction_single_io(user_cond, user_pub, data,
@ -754,7 +758,8 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data,
}
asset = Asset(data, data_id)
tx = Transaction.create([user_pub], [user_pub], data, asset).to_dict()
tx = Transaction.create([user_pub], [([user_pub], 1)],
data, asset).to_dict()
tx.pop('id')
tx['transaction']['metadata'].pop('id')
tx['transaction'].pop('timestamp')
@ -766,17 +771,20 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data,
def test_validate_single_io_create_transaction(user_pub, user_priv, data):
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction.create([user_pub], [user_pub], data, Asset())
tx = Transaction.create([user_pub], [([user_pub], 1)], data, Asset())
tx = tx.sign([user_priv])
assert tx.fulfillments_valid() is True
@mark.skip(reason='Multiple inputs and outputs in CREATE not supported')
# TODO: Add digital assets
def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
user2_pub):
from bigchaindb.common.transaction import Transaction
from bigchaindb.common.transaction import Transaction, Asset, Fulfillment
# a fulfillment for a create transaction with multiple `owners_before`
# is a fulfillment for an implicit threshold condition with
# weight = len(owners_before)
ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict()
ffill.update({'fid': 0})
expected = {
'transaction': {
'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)],
@ -785,45 +793,32 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
'message': 'hello'
}
},
'fulfillments': [
{
'owners_before': [
user_pub,
],
'fid': 0,
'fulfillment': None,
'input': None
},
{
'owners_before': [
user2_pub,
],
'fid': 1,
'fulfillment': None,
'input': None
}
],
'fulfillments': [ffill],
'operation': 'CREATE',
},
'version': 1
}
tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub],
{'message': 'hello'}).to_dict()
asset = Asset(divisible=True)
tx = Transaction.create([user_pub, user2_pub],
[([user_pub], 1), ([user2_pub], 1)],
asset=asset,
metadata={'message': 'hello'}).to_dict()
tx.pop('id')
tx['transaction']['metadata'].pop('id')
tx['transaction'].pop('timestamp')
tx['transaction'].pop('asset')
assert tx == expected
@mark.skip(reason='Multiple inputs and outputs in CREATE not supported')
# TODO: Add digital assets
def test_validate_multiple_io_create_transaction(user_pub, user_priv,
user2_pub, user2_priv):
from bigchaindb.common.transaction import Transaction
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub],
{'message': 'hello'})
tx = Transaction.create([user_pub, user2_pub],
[([user_pub], 1), ([user2_pub], 1)],
metadata={'message': 'hello'},
asset=Asset(divisible=True))
tx = tx.sign([user_priv, user2_priv])
assert tx.fulfillments_valid() is True
@ -862,7 +857,8 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
'version': 1
}
asset = Asset(data, data_id)
tx = Transaction.create([user_pub], [user_pub, user2_pub], data, asset)
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
data, asset)
tx_dict = tx.to_dict()
tx_dict.pop('id')
tx_dict['transaction']['metadata'].pop('id')
@ -876,80 +872,27 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub,
data):
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction.create([user_pub], [user_pub, user2_pub], data, Asset())
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
data, Asset())
tx = tx.sign([user_priv])
assert tx.fulfillments_valid() is True
def test_create_create_transaction_hashlock(user_pub, data, data_id):
from cryptoconditions import PreimageSha256Fulfillment
from bigchaindb.common.transaction import Transaction, Condition, Asset
secret = b'much secret, wow'
hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri
cond = Condition(hashlock)
expected = {
'transaction': {
'conditions': [cond.to_dict(0)],
'metadata': {
'data': data,
},
'asset': {
'id': data_id,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub,
],
'fid': 0,
'fulfillment': None,
'input': None
},
],
'operation': 'CREATE',
},
'version': 1
}
asset = Asset(data, data_id)
tx = Transaction.create([user_pub], [], data, asset, secret).to_dict()
tx.pop('id')
tx['transaction']['metadata'].pop('id')
tx['transaction'].pop('timestamp')
tx['transaction']['fulfillments'][0]['fulfillment'] = None
assert tx == expected
def test_validate_hashlock_create_transaction(user_pub, user_priv, data):
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction.create([user_pub], [], data, Asset(), b'much secret, wow')
tx = tx.sign([user_priv])
assert tx.fulfillments_valid() is True
def test_create_create_transaction_with_invalid_parameters():
def test_create_create_transaction_with_invalid_parameters(user_pub):
from bigchaindb.common.transaction import Transaction
with raises(TypeError):
Transaction.create('not a list')
with raises(TypeError):
Transaction.create([], 'not a list')
with raises(NotImplementedError):
Transaction.create(['a', 'b'], ['c', 'd'])
with raises(NotImplementedError):
Transaction.create(['a'], [], time_expire=123)
with raises(ValueError):
Transaction.create(['a'], [], secret=None)
Transaction.create([], [user_pub])
with raises(ValueError):
Transaction.create([], [], secret='wow, much secret')
Transaction.create([user_pub], [])
with raises(ValueError):
Transaction.create([user_pub], [user_pub])
with raises(ValueError):
Transaction.create([user_pub], [([user_pub],)])
def test_conditions_to_inputs(tx):
@ -995,7 +938,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
}
inputs = tx.to_inputs([0])
asset = Asset(None, data_id)
transfer_tx = Transaction.transfer(inputs, [user2_pub], asset=asset)
transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset)
transfer_tx = transfer_tx.sign([user_priv])
transfer_tx = transfer_tx.to_dict()
transfer_tx_body = transfer_tx['transaction']
@ -1014,16 +957,15 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
assert transfer_tx.fulfillments_valid([tx.conditions[0]]) is True
@mark.skip(reason='FIXME: When divisible assets land')
def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
user2_pub, user2_priv,
user3_pub, user2_cond):
from bigchaindb.common.transaction import Transaction
from bigchaindb.common.transaction import Transaction, Asset
tx1 = Transaction.create([user_pub], [user_pub], {'message': 'hello'})
tx1 = tx1.sign([user_priv])
tx2 = Transaction.create([user2_pub], [user2_pub], {'message': 'hello'})
tx2 = tx2.sign([user2_priv])
asset = Asset(divisible=True)
tx = Transaction.create([user_pub], [([user_pub], 1), ([user2_pub], 1)],
asset=asset, metadata={'message': 'hello'})
tx = tx.sign([user_priv])
expected = {
'transaction': {
@ -1037,7 +979,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx1.id,
'txid': tx.id,
'cid': 0
}
}, {
@ -1047,8 +989,8 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
'fid': 1,
'fulfillment': None,
'input': {
'txid': tx2.id,
'cid': 0
'txid': tx.id,
'cid': 1
}
}
],
@ -1056,30 +998,28 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
},
'version': 1
}
tx1_inputs = tx1.to_inputs()
tx2_inputs = tx2.to_inputs()
tx_inputs = tx1_inputs + tx2_inputs
transfer_tx = Transaction.transfer(tx_inputs, [[user2_pub], [user2_pub]])
transfer_tx = Transaction.transfer(tx.to_inputs(),
[([user2_pub], 1), ([user2_pub], 1)],
asset=tx.asset)
transfer_tx = transfer_tx.sign([user_priv, user2_priv])
transfer_tx = transfer_tx
assert len(transfer_tx.fulfillments) == 2
assert len(transfer_tx.conditions) == 2
combined_conditions = tx1.conditions + tx2.conditions
assert transfer_tx.fulfillments_valid(combined_conditions) is True
assert transfer_tx.fulfillments_valid(tx.conditions) is True
transfer_tx = transfer_tx.to_dict()
transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None
transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None
transfer_tx['transaction'].pop('timestamp')
transfer_tx.pop('id')
transfer_tx['transaction'].pop('asset')
assert expected == transfer_tx
def test_create_transfer_with_invalid_parameters():
def test_create_transfer_with_invalid_parameters(user_pub):
from bigchaindb.common.transaction import Transaction, Asset
with raises(TypeError):
@ -1090,17 +1030,25 @@ def test_create_transfer_with_invalid_parameters():
Transaction.transfer(['fulfillment'], {}, Asset())
with raises(ValueError):
Transaction.transfer(['fulfillment'], [], Asset())
with raises(ValueError):
Transaction.transfer(['fulfillment'], [user_pub], Asset())
with raises(ValueError):
Transaction.transfer(['fulfillment'], [([user_pub],)], Asset())
def test_cant_add_empty_condition():
from bigchaindb.common.transaction import Transaction
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, None)
with raises(TypeError):
tx.add_condition(None)
def test_cant_add_empty_fulfillment():
from bigchaindb.common.transaction import Transaction
from bigchaindb.common.transaction import Transaction, Asset
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, None)
with raises(TypeError):
tx.add_fulfillment(None)

View File

@ -72,7 +72,7 @@ def b(request, node_config):
@pytest.fixture
def create_tx(b, user_vk):
from bigchaindb.models import Transaction
return Transaction.create([b.me], [user_vk])
return Transaction.create([b.me], [([user_vk], 1)])
@pytest.fixture
@ -84,5 +84,5 @@ def signed_create_tx(b, create_tx):
def signed_transfer_tx(signed_create_tx, user_vk, user_sk):
from bigchaindb.models import Transaction
inputs = signed_create_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_vk], signed_create_tx.asset)
tx = Transaction.transfer(inputs, [([user_vk], 1)], signed_create_tx.asset)
return tx.sign([user_sk])

View File

@ -84,7 +84,7 @@ def inputs(user_vk):
prev_block_id = g.id
for block in range(4):
transactions = [
Transaction.create([b.me], [user_vk]).sign([b.me_private])
Transaction.create([b.me], [([user_vk], 1)]).sign([b.me_private])
for i in range(10)
]
block = b.create_block(transactions)

View File

@ -15,7 +15,7 @@ def dummy_tx():
import bigchaindb
from bigchaindb.models import Transaction
b = bigchaindb.Bigchain()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
return tx
@ -37,7 +37,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
block1 = b.create_block([tx])
@ -60,7 +60,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
block1 = b.create_block([tx])
@ -74,7 +74,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -99,7 +99,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -107,13 +107,15 @@ class TestBigchainApi(object):
b.write_block(block1, durability='hard')
monkeypatch.setattr('time.time', lambda: 2)
transfer_tx = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset)
transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
tx.asset)
transfer_tx = transfer_tx.sign([b.me_private])
block2 = b.create_block([transfer_tx])
b.write_block(block2, durability='hard')
monkeypatch.setattr('time.time', lambda: 3)
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset)
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
tx.asset)
transfer_tx2 = transfer_tx2.sign([b.me_private])
block3 = b.create_block([transfer_tx2])
b.write_block(block3, durability='hard')
@ -133,7 +135,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -159,13 +161,13 @@ class TestBigchainApi(object):
b.create_genesis_block()
monkeypatch.setattr('time.time', lambda: 1)
tx1 = Transaction.create([b.me], [b.me])
tx1 = Transaction.create([b.me], [([b.me], 1)])
tx1 = tx1.sign([b.me_private])
block1 = b.create_block([tx1])
b.write_block(block1, durability='hard')
monkeypatch.setattr('time.time', lambda: 2)
tx2 = Transaction.create([b.me], [b.me])
tx2 = Transaction.create([b.me], [([b.me], 1)])
tx2 = tx2.sign([b.me_private])
block2 = b.create_block([tx2])
b.write_block(block2, durability='hard')
@ -185,7 +187,7 @@ class TestBigchainApi(object):
from bigchaindb.models import Transaction
metadata = {'msg': 'Hello BigchainDB!'}
tx = Transaction.create([b.me], [user_vk], metadata=metadata)
tx = Transaction.create([b.me], [([user_vk], 1)], metadata=metadata)
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -205,7 +207,7 @@ class TestBigchainApi(object):
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 = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
response = b.write_transaction(tx)
@ -223,7 +225,7 @@ class TestBigchainApi(object):
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 = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -243,7 +245,7 @@ class TestBigchainApi(object):
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 = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# There's no need to b.write_transaction(tx) to the backlog
@ -267,7 +269,7 @@ class TestBigchainApi(object):
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 = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# Make sure there's a copy of tx in the backlog
@ -537,7 +539,7 @@ class TestBigchainApi(object):
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 = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -563,7 +565,7 @@ class TestBigchainApi(object):
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 = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -587,16 +589,17 @@ class TestBigchainApi(object):
fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_vk),
[user_vk],
TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([fulfillment], [user_vk], Asset())
tx = Transaction.transfer([fulfillment], [([user_vk], 1)], Asset())
with pytest.raises(TransactionDoesNotExist) as excinfo:
with pytest.raises(TransactionDoesNotExist):
tx.validate(Bigchain())
def test_count_backlog(self, b, user_vk):
from bigchaindb.models import Transaction
for _ in range(4):
tx = Transaction.create([b.me], [user_vk]).sign([b.me_private])
tx = Transaction.create([b.me],
[([user_vk], 1)]).sign([b.me_private])
b.write_transaction(tx)
assert b.backend.count_backlog() == 4
@ -638,7 +641,7 @@ class TestTransactionValidation(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_transaction = b.get_transaction(input_tx.txid)
sk, vk = generate_key_pair()
tx = Transaction.create([vk], [user_vk])
tx = Transaction.create([vk], [([user_vk], 1)])
tx.operation = 'TRANSFER'
tx.asset = input_transaction.asset
tx.fulfillments[0].tx_input = input_tx
@ -682,7 +685,8 @@ class TestTransactionValidation(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
transfer_tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user_vk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
@ -706,7 +710,8 @@ class TestTransactionValidation(object):
inputs = input_tx.to_inputs()
# create a transaction that's valid but not in a voted valid block
transfer_tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user_vk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
@ -716,7 +721,8 @@ class TestTransactionValidation(object):
b.write_block(block, durability='hard')
# create transaction with the undecided input
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [user_vk],
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(),
[([user_vk], 1)],
transfer_tx.asset)
tx_invalid = tx_invalid.sign([user_sk])
@ -815,7 +821,7 @@ class TestMultipleInputs(object):
tx_link = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(tx_link.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user2_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user2_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# validate transaction
@ -823,69 +829,6 @@ class TestMultipleInputs(object):
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs')
def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# get inputs
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user_vk]])
tx = tx.sign([user_sk])
assert b.validate_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs')
def test_transfer_single_owners_single_input_from_multiple_outputs(self, b,
user_sk,
user_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# get inputs
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
# create block with the transaction
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# get inputs from user2
owned_inputs = b.get_owned_ids(user2_vk)
assert len(owned_inputs) == len(inputs)
# create a transaction with a single input from a multiple output transaction
tx_link = owned_inputs.pop()
inputs = b.get_transaction(tx_link.txid).to_inputs([0])
tx = Transaction.transfer(inputs, [user_vk])
tx = tx.sign([user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
def test_single_owner_before_multiple_owners_after_single_input(self, b,
user_sk,
user_vk,
@ -899,47 +842,14 @@ class TestMultipleInputs(object):
owned_inputs = b.get_owned_ids(user_vk)
tx_link = owned_inputs.pop()
input_tx = b.get_transaction(tx_link.txid)
tx = Transaction.transfer(input_tx.to_inputs(), [[user2_vk, user3_vk]], input_tx.asset)
tx = Transaction.transfer(input_tx.to_inputs(),
[([user2_vk, user3_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs')
def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b,
user_sk,
user_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk, user3_vk]])
tx = tx.sign([user_sk])
# create block with the transaction
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# validate transaction
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_single_owner_after_single_input(self, b,
user_sk,
@ -950,7 +860,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -963,7 +873,8 @@ class TestMultipleInputs(object):
input_tx = b.get_transaction(owned_input.txid)
inputs = input_tx.to_inputs()
transfer_tx = Transaction.transfer(inputs, [user3_vk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user3_vk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk, user2_sk])
# validate transaction
@ -971,29 +882,6 @@ class TestMultipleInputs(object):
assert len(transfer_tx.fulfillments) == 1
assert len(transfer_tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs_shared')
def test_multiple_owners_before_single_owner_after_multiple_inputs(self, b,
user_sk, user_vk, user2_vk, user2_sk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
# create a new users
user3_sk, user3_vk = crypto.generate_key_pair()
tx_links = b.get_owned_ids(user_vk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user3_vk]])
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_multiple_owners_after_single_input(self, b,
user_sk,
@ -1005,7 +893,7 @@ class TestMultipleInputs(object):
user3_sk, user3_vk = crypto.generate_key_pair()
user4_sk, user4_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1018,38 +906,14 @@ class TestMultipleInputs(object):
tx_link = b.get_owned_ids(user_vk).pop()
tx_input = b.get_transaction(tx_link.txid)
tx = Transaction.transfer(tx_input.to_inputs(), [[user3_vk, user4_vk]], tx_input.asset)
tx = Transaction.transfer(tx_input.to_inputs(),
[([user3_vk, user4_vk], 1)], tx_input.asset)
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs_shared')
def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self, b,
user_sk, user_vk,
user2_sk, user2_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
# create a new users
user3_sk, user3_vk = crypto.generate_key_pair()
user4_sk, user4_vk = crypto.generate_key_pair()
tx_links = b.get_owned_ids(user_vk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user3_vk, user4_vk]])
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_vk):
from bigchaindb.common import crypto
from bigchaindb.common.transaction import TransactionLink
@ -1057,7 +921,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1067,7 +931,7 @@ class TestMultipleInputs(object):
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)]
assert owned_inputs_user2 == []
tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1087,7 +951,7 @@ class TestMultipleInputs(object):
genesis = b.create_genesis_block()
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1103,7 +967,8 @@ class TestMultipleInputs(object):
# NOTE: The transaction itself is valid, still will mark the block
# as invalid to mock the behavior.
tx_invalid = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx_invalid = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)],
tx.asset)
tx_invalid = tx_invalid.sign([user_sk])
block = b.create_block([tx_invalid])
b.write_block(block, durability='hard')
@ -1119,47 +984,45 @@ class TestMultipleInputs(object):
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)]
assert owned_inputs_user2 == []
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk,
user_vk):
import random
from bigchaindb.common import crypto
from bigchaindb.common.transaction import TransactionLink
from bigchaindb.common.transaction import TransactionLink, Asset
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
transactions = []
for i in range(2):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_vk], payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
# create divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 1), ([user_vk], 1)],
asset=asset)
tx_create_signed = tx_create.sign([b.me_private])
block = b.create_block([tx_create_signed])
b.write_block(block, durability='hard')
# get input
owned_inputs_user1 = b.get_owned_ids(user_vk)
owned_inputs_user2 = b.get_owned_ids(user2_vk)
expected_owned_inputs_user1 = [TransactionLink(tx.id, 0) for tx
in transactions]
expected_owned_inputs_user1 = [TransactionLink(tx_create.id, 0),
TransactionLink(tx_create.id, 1)]
assert owned_inputs_user1 == expected_owned_inputs_user1
assert owned_inputs_user2 == []
inputs = sum([tx.to_inputs() for tx in transactions], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
block = b.create_block([tx])
# transfer divisible asset divided in two outputs
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([user2_vk], 1), ([user2_vk], 1)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
block = b.create_block([tx_transfer_signed])
b.write_block(block, durability='hard')
owned_inputs_user1 = b.get_owned_ids(user_vk)
owned_inputs_user2 = b.get_owned_ids(user2_vk)
assert owned_inputs_user1 == []
assert owned_inputs_user2 == [TransactionLink(tx.id, 0),
TransactionLink(tx.id, 1)]
assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0),
TransactionLink(tx_transfer.id, 1)]
def test_get_owned_ids_multiple_owners(self, b, user_sk, user_vk):
from bigchaindb.common import crypto
@ -1169,7 +1032,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1181,7 +1044,7 @@ class TestMultipleInputs(object):
assert owned_inputs_user1 == owned_inputs_user2
assert owned_inputs_user1 == expected_owned_inputs_user1
tx = Transaction.transfer(tx.to_inputs(), [user3_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user3_vk], 1)], tx.asset)
tx = tx.sign([user_sk, user2_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1197,7 +1060,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1211,7 +1074,7 @@ class TestMultipleInputs(object):
assert spent_inputs_user1 is None
# create a transaction and block
tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1228,7 +1091,7 @@ class TestMultipleInputs(object):
# create a new users
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1246,7 +1109,7 @@ class TestMultipleInputs(object):
assert spent_inputs_user1 is None
# create a transaction and block
tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1261,24 +1124,23 @@ class TestMultipleInputs(object):
# Now there should be no spents (the block is invalid)
assert spent_inputs_user1 is None
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_vk):
import random
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# create a new users
user2_sk, user2_vk = crypto.generate_key_pair()
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_vk], payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
# create a divisible asset with 3 outputs
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 1),
([user_vk], 1),
([user_vk], 1)],
asset=asset)
tx_create_signed = tx_create.sign([b.me_private])
block = b.create_block([tx_create_signed])
b.write_block(block, durability='hard')
owned_inputs_user1 = b.get_owned_ids(user_vk)
@ -1287,22 +1149,22 @@ class TestMultipleInputs(object):
for input_tx in owned_inputs_user1:
assert b.get_spent(input_tx.txid, input_tx.cid) is None
# select inputs to use
inputs = sum([tx.to_inputs() for tx in transactions[:2]], [])
# create a transaction and block
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
block = b.create_block([tx])
# transfer the first 2 inputs
tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2],
[([user2_vk], 1), ([user2_vk], 1)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
block = b.create_block([tx_transfer_signed])
b.write_block(block, durability='hard')
# check that used inputs are marked as spent
for ffill in inputs:
assert b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) == tx
for ffill in tx_create.to_inputs()[:2]:
spent_tx = b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid)
assert spent_tx == tx_transfer_signed
# check if remaining transaction that was unspent is also perceived
# spendable by BigchainDB
assert b.get_spent(transactions[2].id, 0) is None
assert b.get_spent(tx_create.to_inputs()[2].tx_input.txid, 2) is None
def test_get_spent_multiple_owners(self, b, user_sk, user_vk):
import random
@ -1315,7 +1177,8 @@ class TestMultipleInputs(object):
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_vk, user2_vk], payload)
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)],
payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
@ -1328,7 +1191,8 @@ class TestMultipleInputs(object):
assert b.get_spent(input_tx.txid, input_tx.cid) is None
# create a transaction
tx = Transaction.transfer(transactions[0].to_inputs(), [user3_vk], transactions[0].asset)
tx = Transaction.transfer(transactions[0].to_inputs(),
[([user3_vk], 1)], transactions[0].asset)
tx = tx.sign([user_sk, user2_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')

View File

@ -45,7 +45,7 @@ def test_create_block(b, user_vk):
block_maker = BlockPipeline()
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block_maker.create(tx)
@ -63,7 +63,7 @@ def test_write_block(b, user_vk):
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
txs.append(tx)
@ -82,7 +82,7 @@ def test_duplicate_transaction(b, user_vk):
txs = []
for i in range(10):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
txs.append(tx)
@ -109,7 +109,7 @@ def test_delete_tx(b, user_vk):
from bigchaindb.pipelines.block import BlockPipeline
block_maker = BlockPipeline()
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block_maker.create(tx)
# make sure the tx appears in the backlog
@ -138,7 +138,8 @@ def test_prefeed(b, user_vk):
from bigchaindb.pipelines.block import initial
for i in range(100):
tx = Transaction.create([b.me], [user_vk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_vk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private])
b.write_transaction(tx)
@ -167,7 +168,8 @@ def test_full_pipeline(b, user_vk):
count_assigned_to_me = 0
for i in range(100):
tx = Transaction.create([b.me], [user_vk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_vk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private]).to_dict()
assignee = random.choice([b.me, 'aaa', 'bbb', 'ccc'])
tx['assignee'] = assignee

View File

@ -15,7 +15,7 @@ def test_check_for_quorum_invalid(b, user_vk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_vk])
tx1 = Transaction.create([b.me], [([user_vk], 1)])
test_block = b.create_block([tx1])
# simulate a federation with four voters
@ -44,7 +44,7 @@ def test_check_for_quorum_invalid_prev_node(b, user_vk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_vk])
tx1 = Transaction.create([b.me], [([user_vk], 1)])
test_block = b.create_block([tx1])
# simulate a federation with four voters
@ -74,7 +74,7 @@ def test_check_for_quorum_valid(b, user_vk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_vk])
tx1 = Transaction.create([b.me], [([user_vk], 1)])
test_block = b.create_block([tx1])
# simulate a federation with four voters
@ -103,7 +103,7 @@ def test_check_requeue_transaction(b, user_vk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_vk])
tx1 = Transaction.create([b.me], [([user_vk], 1)])
test_block = b.create_block([tx1])
e.requeue_transactions(test_block)
@ -131,7 +131,8 @@ def test_full_pipeline(b, user_vk):
# write two blocks
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_vk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_vk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private])
txs.append(tx)
@ -140,7 +141,8 @@ def test_full_pipeline(b, user_vk):
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_vk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_vk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private])
txs.append(tx)

View File

@ -10,7 +10,7 @@ import os
def test_get_stale(b, user_vk):
from bigchaindb.models import Transaction
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx, durability='hard')
@ -27,7 +27,7 @@ def test_get_stale(b, user_vk):
def test_reassign_transactions(b, user_vk):
from bigchaindb.models import Transaction
# test with single node
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx, durability='hard')
@ -36,7 +36,7 @@ def test_reassign_transactions(b, user_vk):
stm.reassign_transactions(tx.to_dict())
# test with federation
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx, durability='hard')
@ -51,7 +51,7 @@ def test_reassign_transactions(b, user_vk):
assert reassigned_tx['assignee'] != tx['assignee']
# test with node not in federation
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private]).to_dict()
tx.update({'assignee': 'lol'})
tx.update({'assignment_timestamp': time.time()})
@ -85,7 +85,7 @@ def test_full_pipeline(monkeypatch, user_vk):
monkeypatch.setattr('time.time', lambda: 1)
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
original_txc.append(tx.to_dict())

View File

@ -8,7 +8,7 @@ from multipipes import Pipe, Pipeline
def dummy_tx(b):
from bigchaindb.models import Transaction
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
return tx
@ -130,7 +130,7 @@ def test_vote_validate_transaction(b):
assert validation == (True, 123, 1)
# NOTE: Submit unsigned transaction to `validate_tx` yields `False`.
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
validation = vote_obj.validate_tx(tx, 456, 10)
assert validation == (False, 456, 10)
@ -224,7 +224,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch):
# create a `CREATE` transaction
test_user_priv, test_user_pub = crypto.generate_key_pair()
tx = Transaction.create([b.me], [test_user_pub])
tx = Transaction.create([b.me], [([test_user_pub], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -265,7 +265,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
# create a `CREATE` transaction
test_user_priv, test_user_pub = crypto.generate_key_pair()
tx = Transaction.create([b.me], [test_user_pub])
tx = Transaction.create([b.me], [([test_user_pub], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -274,7 +274,8 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
# create a `TRANSFER` transaction
test_user2_priv, test_user2_pub = crypto.generate_key_pair()
tx2 = Transaction.transfer(tx.to_inputs(), [test_user2_pub], tx.asset)
tx2 = Transaction.transfer(tx.to_inputs(), [([test_user2_pub], 1)],
tx.asset)
tx2 = tx2.sign([test_user_priv])
monkeypatch.setattr('time.time', lambda: 2)
@ -338,7 +339,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_vk):
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
# NOTE: `tx` is invalid, because it wasn't signed.
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
block = b.create_block([tx])
inpipe.put(block.to_dict())
@ -375,7 +376,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_vk):
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
# NOTE: `tx` is invalid, because its id is not corresponding to its content
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx]).to_dict()
block['block']['transactions'][0]['id'] = 'an invalid tx id'
@ -414,7 +415,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_vk):
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
# NOTE: `tx` is invalid, because its content is not corresponding to its id
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx]).to_dict()
block['block']['transactions'][0]['id'] = 'an invalid tx id'

View File

@ -5,7 +5,7 @@ class TestTransactionModel(object):
def test_validating_an_invalid_transaction(self, b):
from bigchaindb.models import Transaction
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx.operation = 'something invalid'
with raises(TypeError):
@ -41,7 +41,7 @@ class TestBlockModel(object):
from bigchaindb.common.util import gen_timestamp, serialize
from bigchaindb.models import Block, Transaction
transactions = [Transaction.create([b.me], [b.me])]
transactions = [Transaction.create([b.me], [([b.me], 1)])]
timestamp = gen_timestamp()
voters = ['Qaaa', 'Qbbb']
expected_block = {
@ -73,7 +73,7 @@ class TestBlockModel(object):
from bigchaindb.common.util import gen_timestamp, serialize
from bigchaindb.models import Block, Transaction
transactions = [Transaction.create([b.me], [b.me])]
transactions = [Transaction.create([b.me], [([b.me], 1)])]
timestamp = gen_timestamp()
voters = ['Qaaa', 'Qbbb']
expected = Block(transactions, b.me, timestamp, voters)
@ -113,7 +113,7 @@ class TestBlockModel(object):
from bigchaindb.common.util import gen_timestamp, serialize
from bigchaindb.models import Block, Transaction
transactions = [Transaction.create([b.me], [b.me])]
transactions = [Transaction.create([b.me], [([b.me], 1)])]
timestamp = gen_timestamp()
voters = ['Qaaa', 'Qbbb']
@ -136,7 +136,7 @@ class TestBlockModel(object):
def test_compare_blocks(self, b):
from bigchaindb.models import Block, Transaction
transactions = [Transaction.create([b.me], [b.me])]
transactions = [Transaction.create([b.me], [([b.me], 1)])]
assert Block() != 'invalid comparison'
assert Block(transactions) == Block(transactions)
@ -146,7 +146,7 @@ class TestBlockModel(object):
from bigchaindb.common.util import gen_timestamp, serialize
from bigchaindb.models import Block, Transaction
transactions = [Transaction.create([b.me], [b.me])]
transactions = [Transaction.create([b.me], [([b.me], 1)])]
timestamp = gen_timestamp()
voters = ['Qaaa', 'Qbbb']
expected_block = {
@ -168,7 +168,7 @@ class TestBlockModel(object):
from unittest.mock import Mock
from bigchaindb.models import Transaction
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
block = b.create_block([tx])
has_previous_vote = Mock()

View File

@ -36,7 +36,7 @@ def test_post_create_transaction_endpoint(b, client):
from bigchaindb.models import Transaction
user_priv, user_pub = crypto.generate_key_pair()
tx = Transaction.create([user_pub], [user_pub])
tx = Transaction.create([user_pub], [([user_pub], 1)])
tx = tx.sign([user_priv])
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
@ -48,7 +48,7 @@ def test_post_create_transaction_with_invalid_id(b, client):
from bigchaindb.models import Transaction
user_priv, user_pub = crypto.generate_key_pair()
tx = Transaction.create([user_pub], [user_pub])
tx = Transaction.create([user_pub], [([user_pub], 1)])
tx = tx.sign([user_priv]).to_dict()
tx['id'] = 'invalid id'
@ -60,7 +60,7 @@ def test_post_create_transaction_with_invalid_signature(b, client):
from bigchaindb.models import Transaction
user_priv, user_pub = crypto.generate_key_pair()
tx = Transaction.create([user_pub], [user_pub])
tx = Transaction.create([user_pub], [([user_pub], 1)])
tx = tx.sign([user_priv]).to_dict()
tx['transaction']['fulfillments'][0]['fulfillment'] = 'invalid signature'
@ -77,7 +77,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_vk, user_sk):
input_valid = b.get_owned_ids(user_vk).pop()
create_tx = b.get_transaction(input_valid.txid)
transfer_tx = Transaction.transfer(create_tx.to_inputs(), [user_pub], create_tx.asset)
transfer_tx = Transaction.transfer(create_tx.to_inputs(),
[([user_pub], 1)], create_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
@ -94,7 +95,8 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_vk, user_
input_valid = b.get_owned_ids(user_vk).pop()
create_tx = b.get_transaction(input_valid.txid)
transfer_tx = Transaction.transfer(create_tx.to_inputs(), [user_pub], create_tx.asset)
transfer_tx = Transaction.transfer(create_tx.to_inputs(),
[([user_pub], 1)], create_tx.asset)
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
assert res.status_code == 400