Merge remote-tracking branch 'origin/master' into feat/746/new-naming-convention-for-keys

This commit is contained in:
Rodolphe Marques 2016-11-17 11:41:23 +01:00
commit eead7dbdda
32 changed files with 1733 additions and 722 deletions

View File

@ -82,7 +82,7 @@ How? Let's split the command down into its components:
- `install` tells pip to use the *install* action
- `-e` installs a project in [editable mode](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs)
- `.` installs what's in the current directory
- `[dev]` adds some [extra requirements](https://pythonhosted.org/setuptools/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) to the installation. (If you are curious, open `setup.py` and look for `dev` in the `extras_require` section.)
- `[dev]` adds some [extra requirements](https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) to the installation. (If you are curious, open `setup.py` and look for `dev` in the `extras_require` section.)
Aside: An alternative to `pip install -e .[dev]` is `python setup.py develop`.

View File

@ -65,6 +65,14 @@ x = 'name: {}; score: {}'.format(name, n)
we use the `format()` version. The [official Python documentation says](https://docs.python.org/2/library/stdtypes.html#str.format), "This method of string formatting is the new standard in Python 3, and should be preferred to the % formatting described in String Formatting Operations in new code."
## Runnng the Flake8 Style Checker
We use [Flake8](http://flake8.pycqa.org/en/latest/index.html) to check our Python code style. Once you have it installed, you can run it using:
```text
flake8 --max-line-length 119 bigchaindb/
```
## Writing and Running (Python) Unit Tests
We write unit tests for our Python code using the [pytest](http://pytest.org/latest/) framework.

23
Release_Process.md Normal file
View File

@ -0,0 +1,23 @@
# Our Release Process
This is a summary of the steps we go through to release a new version of BigchainDB Server.
1. Update the `CHANGELOG.md` file
2. Update the version numbers in `bigchaindb/version.py`. Note that we try to use [semantic versioning](http://semver.org/) (i.e. MAJOR.MINOR.PATCH)
3. Go to the [bigchaindb/bigchaindb Releases page on GitHub](https://github.com/bigchaindb/bigchaindb/releases)
and click the "Draft a new release" button
4. Name the tag something like v0.7.0
5. The target should be a specific commit: the one when the update of `bigchaindb/version.py` got merged into master
6. The release title should be something like v0.7.0
7. The description should be copied from the `CHANGELOG.md` file updated above
8. Generate and send the latest `bigchaindb` package to PyPI. Dimi and Sylvain can do this, maybe others
9. Login to readthedocs.org as a maintainer of the BigchainDB Server docs.
Go to Admin --> Versions and under **Choose Active Versions**, make sure that the new version's tag is
"Active" and "Public"
After the release:
1. Update `bigchaindb/version.py` again, to be something like 0.8.0.dev (with a dev on the end).
This is so people reading the latest docs will know that they're for the latest (master branch)
version of BigchainDB Server, not the docs at the time of the most recent release (which are also
available).

View File

@ -1,6 +1,5 @@
"""Implementation of the `bigchaindb` command,
which is one of the commands in the BigchainDB
command-line interface.
the command-line interface (CLI) for BigchainDB Server.
"""
import os
@ -194,7 +193,7 @@ def _run_load(tx_left, stats):
b = bigchaindb.Bigchain()
while True:
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx)

View File

@ -14,5 +14,6 @@ def generate_key_pair():
private_key, public_key = crypto.ed25519_generate_key_pair()
return private_key.decode(), public_key.decode()
PrivateKey = crypto.Ed25519SigningKey
PublicKey = crypto.Ed25519VerifyingKey

View File

@ -69,9 +69,9 @@ class CyclicBlockchainError(Exception):
"""Raised when there is a cycle in the blockchain"""
class FulfillmentNotInValidBlock(Exception):
"""Raised when a transaction depends on an invalid or undecided
fulfillment"""
class TransactionNotInValidBlock(Exception):
"""Raised when a transfer transaction is attempting to fulfill the
conditions of a transaction that is in an invalid or undecided block"""
class AssetIdMismatch(Exception):

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 PrivateKey, 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)
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,14 +336,11 @@ class Condition(object):
Returns:
:class:`cryptoconditions.ThresholdSha256Fulfillment`:
"""
if isinstance(current, tuple):
owners_after, threshold = current
else:
owners_after = current
try:
threshold = len(owners_after)
except TypeError:
threshold = None
owners_after = current
try:
threshold = len(owners_after)
except TypeError:
threshold = None
if isinstance(owners_after, list) and len(owners_after) > 1:
ffill = ThresholdSha256Fulfillment(threshold=threshold)
@ -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() == other.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)
@ -786,20 +860,14 @@ class Transaction(object):
:obj:`list` of :class:`~bigchaindb.common.transaction.
Fulfillment`
"""
inputs = []
if condition_indices is None or len(condition_indices) == 0:
# NOTE: If no condition indices are passed, we just assume to
# take all conditions as inputs.
condition_indices = [index for index, _
in enumerate(self.conditions)]
for cid in condition_indices:
input_cond = self.conditions[cid]
ffill = Fulfillment(input_cond.fulfillment,
input_cond.owners_after,
TransactionLink(self.id, cid))
inputs.append(ffill)
return inputs
# NOTE: If no condition indices are passed, we just assume to
# take all conditions as inputs.
return [
Fulfillment(self.conditions[cid].fulfillment,
self.conditions[cid].owners_after,
TransactionLink(self.id, cid))
for cid in condition_indices or range(len(self.conditions))
]
def add_fulfillment(self, fulfillment):
"""Adds a Fulfillment to a Transaction's list of Fulfillments.
@ -868,13 +936,12 @@ class Transaction(object):
key_pairs = {gen_public_key(PrivateKey(private_key)):
PrivateKey(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()
@ -1031,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)
@ -1049,14 +1115,13 @@ 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)
partial_transactions = map(gen_tx, self.fulfillments,
self.conditions, input_condition_uris)
return all(partial_transactions)
@staticmethod
def _fulfillment_valid(fulfillment, operation, tx_serialized,
@ -1214,7 +1279,10 @@ class Transaction(object):
conditions = [Condition.from_dict(condition) for condition
in tx['conditions']]
metadata = Metadata.from_dict(tx['metadata'])
asset = Asset.from_dict(tx['asset'])
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,12 +182,35 @@ 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.TransactionNotInValidBlock, exceptions.AmountError):
return False
def get_block(self, block_id, include_status=False):
"""Get the block with the specified `block_id` (and optionally its status)
Returns the block corresponding to `block_id` or None if no match is
found.
Args:
block_id (str): transaction id of the transaction to get
include_status (bool): also return the status of the block
the return value is then a tuple: (block, status)
"""
block = self.backend.get_block(block_id)
status = None
if include_status:
if block:
status = self.block_election_status(block_id,
block['block']['voters'])
return block, status
else:
return block
def get_transaction(self, txid, include_status=False):
"""Get the transaction with the specified `txid` (and optionally its status)
@ -343,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.
@ -511,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.
@ -258,6 +278,17 @@ class RethinkDBBackend:
r.table('bigchain')
.insert(r.json(block), durability=durability))
def get_block(self, block_id):
"""Get a block from the bigchain table
Args:
block_id (str): block id of the block to get
Returns:
block (dict): the block or `None`
"""
return self.connection.run(r.table('bigchain').get(block_id))
def has_transaction(self, transaction_id):
"""Check if a transaction exists in the bigchain table.
@ -282,6 +313,17 @@ class RethinkDBBackend:
r.table('bigchain', read_mode=self.read_mode)
.count())
def count_backlog(self):
"""Count the number of transactions in the backlog table.
Returns:
The number of transactions in the backlog.
"""
return self.connection.run(
r.table('backlog', read_mode=self.read_mode)
.count())
def write_vote(self, vote):
"""Write a vote to the votes table.
@ -295,6 +337,17 @@ class RethinkDBBackend:
r.table('votes')
.insert(vote))
def get_genesis_block(self):
"""Get the genesis block
Returns:
The genesis block
"""
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(util.is_genesis_block)
.nth(0))
def get_last_voted_block(self, node_pubkey):
"""Get the last voted block for a specific node.
@ -319,10 +372,7 @@ class RethinkDBBackend:
except r.ReqlNonExistenceError:
# return last vote if last vote exists else return Genesis block
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(util.is_genesis_block)
.nth(0))
return self.get_genesis_block()
# Now the fun starts. Since the resolution of timestamp is a second,
# we might have more than one vote per timestamp. If this is the case

View File

@ -2,42 +2,12 @@ from bigchaindb.common.crypto import hash_data, PublicKey, PrivateKey
from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
OperationError, DoubleSpend,
TransactionDoesNotExist,
FulfillmentNotInValidBlock,
AssetIdMismatch)
TransactionNotInValidBlock,
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.
@ -54,6 +24,8 @@ class Transaction(Transaction):
OperationError: if the transaction operation is not supported
TransactionDoesNotExist: if the input of the transaction is not
found
TransactionNotInValidBlock: if the input of the transaction is not
in a valid block
TransactionOwnerError: if the new transaction is using an input it
doesn't own
DoubleSpend: if the transaction is a double spend
@ -71,7 +43,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 +52,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
@ -90,7 +64,7 @@ class Transaction(Transaction):
.format(input_txid))
if status != bigchain.TX_VALID:
raise FulfillmentNotInValidBlock(
raise TransactionNotInValidBlock(
'input `{}` does not exist in a valid block'.format(
input_txid))
@ -101,11 +75,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 {}.'
@ -118,8 +115,36 @@ class Transaction(Transaction):
class Block(object):
"""Bundle a list of Transactions in a Block. Nodes vote on its validity.
Attributes:
transaction (:obj:`list` of :class:`~.Transaction`):
Transactions to be included in the Block.
node_pubkey (str): The public key of the node creating the
Block.
timestamp (str): The Unix time a Block was created.
voters (:obj:`list` of :obj:`str`): A list of a federation
nodes' public keys supposed to vote on the Block.
signature (str): A cryptographic signature ensuring the
integrity and validity of the creator of a Block.
"""
def __init__(self, transactions=None, node_pubkey=None, timestamp=None,
voters=None, signature=None):
"""The Block model is mainly used for (de)serialization and integrity
checking.
Args:
transaction (:obj:`list` of :class:`~.Transaction`):
Transactions to be included in the Block.
node_pubkey (str): The public key of the node creating the
Block.
timestamp (str): The Unix time a Block was created.
voters (:obj:`list` of :obj:`str`): A list of a federation
nodes' public keys supposed to vote on the Block.
signature (str): A cryptographic signature ensuring the
integrity and validity of the creator of a Block.
"""
if transactions is not None and not isinstance(transactions, list):
raise TypeError('`transactions` must be a list instance or None')
else:
@ -146,18 +171,20 @@ class Block(object):
return self.to_dict() == other
def validate(self, bigchain):
"""Validate a block.
"""Validate the Block.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
bigchain (:class:`~bigchaindb.Bigchain`): An instantiated Bigchain
object.
Returns:
block (Block): The block as a `Block` object if it is valid.
Else it raises an appropriate exception describing
the reason of invalidity.
:class:`~.Block`: If valid, return a `Block` object. Else an
appropriate exception describing the reason of invalidity is
raised.
Raises:
OperationError: if a non-federation node signed the block.
OperationError: If a non-federation node signed the Block.
InvalidSignature: If a Block's signature is invalid.
"""
# First, make sure this node hasn't already voted on this block
@ -182,6 +209,15 @@ class Block(object):
return self
def sign(self, private_key):
"""Create a signature for the Block and overwrite `self.signature`.
Args:
private_key (str): A private key corresponding to
`self.node_pubkey`.
Returns:
:class:`~.Block`
"""
block_body = self.to_dict()
block_serialized = serialize(block_body['block'])
private_key = PrivateKey(private_key)
@ -189,8 +225,13 @@ class Block(object):
return self
def is_signature_valid(self):
"""Check the validity of a Block's signature.
Returns:
bool: Stating the validity of the Block's signature.
"""
block = self.to_dict()['block']
# cc only accepts bytesting messages
# cc only accepts bytestring messages
block_serialized = serialize(block).encode()
public_key = PublicKey(block['node_pubkey'])
try:
@ -202,6 +243,21 @@ class Block(object):
@classmethod
def from_dict(cls, block_body):
"""Transform a Python dictionary to a Block object.
Args:
block_body (dict): A block dictionary to be transformed.
Returns:
:class:`~Block`
Raises:
InvalidHash: If the block's id is not corresponding to its
data.
InvalidSignature: If the block's signature is not corresponding
to it's data or `node_pubkey`.
"""
# TODO: Reuse `is_signature_valid` method here.
block = block_body['block']
block_serialized = serialize(block)
block_id = hash_data(block_serialized)
@ -237,6 +293,14 @@ class Block(object):
return self.to_dict()['id']
def to_dict(self):
"""Transform the Block to a Python dictionary.
Returns:
dict: The Block as a dict.
Raises:
OperationError: If the Block doesn't contain any transactions.
"""
if len(self.transactions) == 0:
raise OperationError('Empty block creation is not allowed')

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

@ -49,19 +49,22 @@ class StandaloneApplication(gunicorn.app.base.BaseApplication):
return self.application
def create_app(settings):
def create_app(*, debug=False, threads=4):
"""Return an instance of the Flask application.
Args:
debug (bool): a flag to activate the debug mode for the app
(default: False).
threads (int): number of threads to use
Return:
an instance of the Flask application.
"""
app = Flask(__name__)
app.debug = settings.get('debug', False)
app.debug = debug
app.config['bigchain_pool'] = util.pool(Bigchain, size=settings.get('threads', 4))
app.config['bigchain_pool'] = util.pool(Bigchain, size=threads)
app.config['monitor'] = Monitor()
app.register_blueprint(info_views, url_prefix='/')
@ -88,6 +91,7 @@ def create_server(settings):
if not settings.get('threads'):
settings['threads'] = (multiprocessing.cpu_count() * 2) + 1
app = create_app(settings)
app = create_app(debug=settings.get('debug', False),
threads=settings['threads'])
standalone = StandaloneApplication(app, settings)
return standalone

View File

@ -111,6 +111,7 @@ class TransactionListApi(Resource):
return tx
transaction_api.add_resource(TransactionApi,
'/transactions/<string:tx_id>',
strict_slashes=False)

View File

@ -58,6 +58,9 @@ At a high level, one can communicate with a BigchainDB cluster (set of nodes) us
<div class="buttondiv">
<a class="button" href="http://docs.bigchaindb.com/projects/py-driver/en/latest/index.html">Python Driver Docs</a>
</div>
<div class="buttondiv">
<a class="button" href="https://docs.bigchaindb.com/projects/cli/en/latest/">Command Line Transaction Tool</a>
</div>
<div class="buttondiv">
<a class="button" href="http://docs.bigchaindb.com/projects/server/en/latest/index.html">Server Docs</a>
</div>

View File

@ -3,21 +3,35 @@ The HTTP Client-Server API
.. note::
The HTTP client-server API is currently quite rudimentary. For example, there is no ability to do complex queries using the HTTP API. We plan to add querying capabilities in the future.
The HTTP client-server API is currently quite rudimentary. For example,
there is no ability to do complex queries using the HTTP API. We plan to add
querying capabilities in the future.
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at the address stored in the BigchainDB node configuration settings. The default is:
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at
the address stored in the BigchainDB node configuration settings. The default
is:
`http://localhost:9984/api/v1/ <http://localhost:9984/api/v1/>`_
but that address can be changed by changing the "API endpoint" configuration setting (e.g. in a local config file). There's more information about setting the API endpoint in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
but that address can be changed by changing the "API endpoint" configuration
setting (e.g. in a local config file). There's more information about setting
the API endpoint in :doc:`the section about BigchainDB Configuration Settings
<../server-reference/configuration>`.
There are other configuration settings related to the web server (serving the HTTP API). In particular, the default is for the web server socket to bind to ``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more details, see the "server" settings ("bind", "workers" and "threads") in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
There are other configuration settings related to the web server (serving the
HTTP API). In particular, the default is for the web server socket to bind to
``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more
details, see the "server" settings ("bind", "workers" and "threads") in
:doc:`the section about BigchainDB Configuration Settings
<../server-reference/configuration>`.
API Root
--------
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no ``/api/v1/`` on the end), then you should get an HTTP response with something like the following in the body:
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no
``/api/v1/`` on the end), then you should get an HTTP response with something
like the following in the body:
.. code-block:: json
@ -39,8 +53,13 @@ POST /transactions/
.. http:post:: /transactions/
Push a new transaction.
Note: The posted transaction should be valid `transaction <https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_. The steps to build a valid transaction are beyond the scope of this page. One would normally use a driver such as the `BigchainDB Python Driver <https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to build a valid transaction.
Note: The posted transaction should be valid `transaction
<https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_.
The steps to build a valid transaction are beyond the scope of this page.
One would normally use a driver such as the `BigchainDB Python Driver
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to
build a valid transaction.
**Example request**:
@ -158,9 +177,11 @@ GET /transactions/{tx_id}/status
.. http:get:: /transactions/{tx_id}/status
Get the status of the transaction with the ID ``tx_id``, if a transaction with that ``tx_id`` exists.
Get the status of the transaction with the ID ``tx_id``, if a transaction
with that ``tx_id`` exists.
The possible status values are ``backlog``, ``undecided``, ``valid`` or ``invalid``.
The possible status values are ``backlog``, ``undecided``, ``valid`` or
``invalid``.
:param tx_id: transaction ID
:type tx_id: hex string
@ -194,7 +215,8 @@ GET /transactions/{tx_id}
Get the transaction with the ID ``tx_id``.
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED`` block on ``bigchain``, if exists.
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED``
block on ``bigchain``, if exists.
:param tx_id: transaction ID
:type tx_id: hex string
@ -260,4 +282,46 @@ GET /transactions/{tx_id}
}
:statuscode 200: A transaction with that ID was found.
:statuscode 404: A transaction with that ID was not found.
:statuscode 404: A transaction with that ID was not found.
GET /unspents/
-------------------------
.. http:get:: /unspents?owner_after={owner_after}
Get a list of links to transactions' conditions that have not been used in
a previous transaction and could hence be called unspent conditions/outputs
(or simply: unspents).
This endpoint will return a ``HTTP 400 Bad Request`` if the querystring
``owner_after`` happens to not be defined in the request.
Note that if unspents for a certain ``owner_after`` have not been found by
the server, this will result in the server returning a 200 OK HTTP status
code and an empty list in the response's body.
:param owner_after: A public key, able to validly spend an output of a transaction, assuming the user also has the corresponding private key.
:type owner_after: base58 encoded string
**Example request**:
.. sourcecode:: http
GET /unspents?owner_after=1AAAbbb...ccc HTTP/1.1
Host: example.com
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
[
'../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0',
'../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1'
]
:statuscode 200: A list of outputs were found and returned in the body of the response.
:statuscode 400: The request wasn't understood by the server, e.g. the ``owner_after`` querystring was not included in the request.

View File

@ -1,10 +1,17 @@
Drivers & Clients
=================
Currently, the only language-native driver is written in the Python language.
We also provide the Transaction CLI to be able to script the building of
transactions. You may be able to wrap this tool inside the language of
your choice, and then use the HTTP API directly to post transactions.
.. toctree::
:maxdepth: 1
http-client-server-api
python-driver
The Python Driver <https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>
Transaction CLI <https://docs.bigchaindb.com/projects/cli/en/latest/>
example-apps

View File

@ -1,7 +0,0 @@
# The Python Driver
The BigchainDB Python Driver is a Python wrapper around the [HTTP Client-Server API](http-client-server-api.html). A developer can use it to develop a Python app that communicates with one or more BigchainDB clusters.
The BigchainDB Python Driver documentation is at:
[http://docs.bigchaindb.com/projects/py-driver/en/latest/index.html](http://docs.bigchaindb.com/projects/py-driver/en/latest/index.html)

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_pk, user_sk):
tx_input = b.get_owned_ids(user_pk).pop()
tx_create = b.get_transaction(tx_input.txid)
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
@ -18,61 +20,40 @@ def test_asset_transfer(b, user_pk, user_sk):
def test_validate_bad_asset_creation(b, user_pk):
from bigchaindb.models import Transaction
from bigchaindb.models import Transaction, Asset
# `divisible` needs to be a boolean
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx.asset.divisible = 1
tx_signed = tx.sign([b.me_private])
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_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx.asset.refillable = 1
tx_signed = tx.sign([b.me_private])
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_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx.asset.updatable = 1
tx_signed = tx.sign([b.me_private])
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_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx.asset.data = 'a'
tx_signed = tx.sign([b.me_private])
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_pk, 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_pk, 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_pk, 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_pk, user_sk):
@ -81,7 +62,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_pk, user_sk):
tx_create = b.get_owned_ids(user_pk).pop()
tx_create = b.get_transaction(tx_create.txid)
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 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_pk, user_sk):
def test_get_asset_id_create_transaction(b, user_pk):
from bigchaindb.models import Transaction, Asset
tx_create = Transaction.create([b.me], [user_pk])
tx_create = Transaction.create([b.me], [([user_pk], 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_pk, user_sk):
tx_create = b.get_owned_ids(user_pk).pop()
tx_create = b.get_transaction(tx_create.txid)
# create a transfer transaction
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 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_pk):
from bigchaindb.models import Transaction, Asset
from bigchaindb.common.exceptions import AssetIdMismatch
tx1 = Transaction.create([b.me], [user_pk])
tx2 = Transaction.create([b.me], [user_pk])
tx1 = Transaction.create([b.me], [([user_pk], 1)])
tx2 = Transaction.create([b.me], [([user_pk], 1)])
with pytest.raises(AssetIdMismatch):
Asset.get_asset_id([tx1, tx2])
@ -144,7 +125,7 @@ def test_get_txs_by_asset_id(b, user_pk, user_sk):
assert txs[0].asset.data_id == asset_id
# create a transfer transaction
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 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_pk, 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,7 +275,8 @@ def test_invalid_transaction_initialization():
def test_create_default_asset_on_tx_initialization():
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction(Transaction.CREATE, None)
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, None)
expected = Asset()
asset = tx.asset
@ -510,10 +465,72 @@ 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_eq_asset_link():
from bigchaindb.common.transaction import AssetLink
asset_id_1 = 'asset_1'
asset_id_2 = 'asset_2'
assert AssetLink(asset_id_1) == AssetLink(asset_id_1)
assert AssetLink(asset_id_1) != AssetLink(asset_id_2)
def test_add_fulfillment_to_tx(user_ffill):
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction(Transaction.CREATE, Asset(), [], [])
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset(), [], [])
tx.add_fulfillment(user_ffill)
assert len(tx.fulfillments) == 1
@ -522,7 +539,8 @@ def test_add_fulfillment_to_tx(user_ffill):
def test_add_fulfillment_to_tx_with_invalid_parameters():
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction(Transaction.CREATE, Asset())
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset())
with raises(TypeError):
tx.add_fulfillment('somewronginput')
@ -530,7 +548,8 @@ def test_add_fulfillment_to_tx_with_invalid_parameters():
def test_add_condition_to_tx(user_cond):
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction(Transaction.CREATE, Asset())
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset())
tx.add_condition(user_cond)
assert len(tx.conditions) == 1
@ -539,7 +558,8 @@ def test_add_condition_to_tx(user_cond):
def test_add_condition_to_tx_with_invalid_parameters():
from bigchaindb.common.transaction import Transaction, Asset
tx = Transaction(Transaction.CREATE, Asset(), [], [])
with patch.object(Asset, 'validate_asset', return_value=None):
tx = Transaction(Transaction.CREATE, Asset(), [], [])
with raises(TypeError):
tx.add_condition('somewronginput')
@ -614,16 +634,14 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv):
from bigchaindb.common.crypto import PrivateKey
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 +692,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 +733,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 +768,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 +781,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 +803,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 +867,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 +882,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 +948,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 +967,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 +989,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 +999,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 +1008,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 +1040,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
tx = Transaction(Transaction.CREATE, None)
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
tx = Transaction(Transaction.CREATE, None)
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_pk):
from bigchaindb.models import Transaction
return Transaction.create([b.me], [user_pk])
return Transaction.create([b.me], [([user_pk], 1)])
@pytest.fixture
@ -84,5 +84,5 @@ def signed_create_tx(b, create_tx):
def signed_transfer_tx(signed_create_tx, user_pk, user_sk):
from bigchaindb.models import Transaction
inputs = signed_create_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], signed_create_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 1)], signed_create_tx.asset)
return tx.sign([user_sk])

View File

@ -84,7 +84,7 @@ def inputs(user_pk):
prev_block_id = g.id
for block in range(4):
transactions = [
Transaction.create([b.me], [user_pk]).sign([b.me_private])
Transaction.create([b.me], [([user_pk], 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_pk], metadata=metadata)
tx = Transaction.create([b.me], [([user_pk], 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_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 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_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 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_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 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_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# Make sure there's a copy of tx in the backlog
@ -369,6 +371,15 @@ class TestBigchainApi(object):
assert excinfo.value.args[0] == 'Empty block creation is not allowed'
@pytest.mark.usefixtures('inputs')
def test_get_block_by_id(self, b):
new_block = dummy_block()
b.write_block(new_block, durability='hard')
assert b.get_block(new_block.id) == new_block.to_dict()
block, status = b.get_block(new_block.id, include_status=True)
assert status == b.BLOCK_UNDECIDED
def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b):
import rethinkdb as r
from bigchaindb import util
@ -528,7 +539,7 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -554,7 +565,7 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -578,11 +589,21 @@ class TestBigchainApi(object):
fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_pk),
[user_pk],
TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([fulfillment], [user_pk], Asset())
tx = Transaction.transfer([fulfillment], [([user_pk], 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], 1)]).sign([b.me_private])
b.write_transaction(tx)
assert b.backend.count_backlog() == 4
class TestTransactionValidation(object):
def test_create_operation_with_inputs(self, b, user_pk, create_tx):
@ -620,7 +641,7 @@ class TestTransactionValidation(object):
input_tx = b.get_owned_ids(user_pk).pop()
input_transaction = b.get_transaction(input_tx.txid)
sk, pk = generate_key_pair()
tx = Transaction.create([pk], [user_pk])
tx = Transaction.create([pk], [([user_pk], 1)])
tx.operation = 'TRANSFER'
tx.asset = input_transaction.asset
tx.fulfillments[0].tx_input = input_tx
@ -664,7 +685,8 @@ class TestTransactionValidation(object):
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
transfer_tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user_pk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
@ -679,16 +701,17 @@ class TestTransactionValidation(object):
assert transfer_tx == b.validate_transaction(transfer_tx)
@pytest.mark.usefixtures('inputs')
def test_fulfillment_not_in_valid_block(self, b, user_pk, user_sk):
def test_transaction_not_in_valid_block(self, b, user_pk, user_sk):
from bigchaindb.models import Transaction
from bigchaindb.common.exceptions import FulfillmentNotInValidBlock
from bigchaindb.common.exceptions import TransactionNotInValidBlock
input_tx = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
# create a transaction that's valid but not in a voted valid block
transfer_tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user_pk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
@ -698,11 +721,12 @@ class TestTransactionValidation(object):
b.write_block(block, durability='hard')
# create transaction with the undecided input
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [user_pk],
transfer_tx.asset)
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(),
[([user_pk], 1)],
transfer_tx.asset)
tx_invalid = tx_invalid.sign([user_sk])
with pytest.raises(FulfillmentNotInValidBlock):
with pytest.raises(TransactionNotInValidBlock):
b.validate_transaction(tx_invalid)
@ -797,7 +821,7 @@ class TestMultipleInputs(object):
tx_link = b.get_owned_ids(user_pk).pop()
input_tx = b.get_transaction(tx_link.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user2_pk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user2_pk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# validate transaction
@ -805,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_pk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_pk = crypto.generate_key_pair()
# get inputs
owned_inputs = b.get_owned_ids(user_pk)
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_pk]])
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_pk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_pk = crypto.generate_key_pair()
# get inputs
owned_inputs = b.get_owned_ids(user_pk)
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_pk]])
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_pk)
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_pk])
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_pk,
@ -881,47 +842,14 @@ class TestMultipleInputs(object):
owned_inputs = b.get_owned_ids(user_pk)
tx_link = owned_inputs.pop()
input_tx = b.get_transaction(tx_link.txid)
tx = Transaction.transfer(input_tx.to_inputs(), [[user2_pk, user3_pk]], input_tx.asset)
tx = Transaction.transfer(input_tx.to_inputs(),
[([user2_pk, user3_pk], 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_pk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_pk = crypto.generate_key_pair()
user3_sk, user3_pk = crypto.generate_key_pair()
owned_inputs = b.get_owned_ids(user_pk)
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_pk, user3_pk]])
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,
@ -932,7 +860,7 @@ class TestMultipleInputs(object):
user2_sk, user2_pk = crypto.generate_key_pair()
user3_sk, user3_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk, user2_pk])
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -945,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_pk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user3_pk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk, user2_sk])
# validate transaction
@ -953,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_pk, user2_pk, user2_sk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
# create a new users
user3_sk, user3_pk = crypto.generate_key_pair()
tx_links = b.get_owned_ids(user_pk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user3_pk]])
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,
@ -987,7 +893,7 @@ class TestMultipleInputs(object):
user3_sk, user3_pk = crypto.generate_key_pair()
user4_sk, user4_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk, user2_pk])
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1000,38 +906,14 @@ class TestMultipleInputs(object):
tx_link = b.get_owned_ids(user_pk).pop()
tx_input = b.get_transaction(tx_link.txid)
tx = Transaction.transfer(tx_input.to_inputs(), [[user3_pk, user4_pk]], tx_input.asset)
tx = Transaction.transfer(tx_input.to_inputs(),
[([user3_pk, user4_pk], 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_pk,
user2_sk, user2_pk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
# create a new users
user3_sk, user3_pk = crypto.generate_key_pair()
user4_sk, user4_pk = crypto.generate_key_pair()
tx_links = b.get_owned_ids(user_pk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user3_pk, user4_pk]])
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_pk):
from bigchaindb.common import crypto
from bigchaindb.common.transaction import TransactionLink
@ -1039,7 +921,7 @@ class TestMultipleInputs(object):
user2_sk, user2_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1049,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_pk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1069,7 +951,7 @@ class TestMultipleInputs(object):
genesis = b.create_genesis_block()
user2_sk, user2_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1085,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_pk], tx.asset)
tx_invalid = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)],
tx.asset)
tx_invalid = tx_invalid.sign([user_sk])
block = b.create_block([tx_invalid])
b.write_block(block, durability='hard')
@ -1101,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_pk):
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_pk = crypto.generate_key_pair()
transactions = []
for i in range(2):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_pk], 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_pk], 1), ([user_pk], 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_pk)
owned_inputs_user2 = b.get_owned_ids(user2_pk)
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_pk]])
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_pk], 1), ([user2_pk], 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_pk)
owned_inputs_user2 = b.get_owned_ids(user2_pk)
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_pk):
from bigchaindb.common import crypto
@ -1151,7 +1032,7 @@ class TestMultipleInputs(object):
user2_sk, user2_pk = crypto.generate_key_pair()
user3_sk, user3_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk, user2_pk])
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1163,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_pk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user3_pk], 1)], tx.asset)
tx = tx.sign([user_sk, user2_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1179,7 +1060,7 @@ class TestMultipleInputs(object):
user2_sk, user2_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1193,7 +1074,7 @@ class TestMultipleInputs(object):
assert spent_inputs_user1 is None
# create a transaction and block
tx = Transaction.transfer(tx.to_inputs(), [user2_pk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1210,7 +1091,7 @@ class TestMultipleInputs(object):
# create a new users
user2_sk, user2_pk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1228,7 +1109,7 @@ class TestMultipleInputs(object):
assert spent_inputs_user1 is None
# create a transaction and block
tx = Transaction.transfer(tx.to_inputs(), [user2_pk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1243,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_pk):
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_pk = crypto.generate_key_pair()
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_pk], 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_pk], 1),
([user_pk], 1),
([user_pk], 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_pk)
@ -1269,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_pk]])
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_pk], 1), ([user2_pk], 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_pk):
import random
@ -1297,7 +1177,8 @@ class TestMultipleInputs(object):
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_pk, user2_pk], payload)
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)],
payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
@ -1310,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_pk], transactions[0].asset)
tx = Transaction.transfer(transactions[0].to_inputs(),
[([user3_pk], 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_pk):
block_maker = BlockPipeline()
for i in range(100):
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
block_maker.create(tx)
@ -63,7 +63,7 @@ def test_write_block(b, user_pk):
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
txs.append(tx)
@ -82,7 +82,7 @@ def test_duplicate_transaction(b, user_pk):
txs = []
for i in range(10):
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
txs.append(tx)
@ -109,7 +109,7 @@ def test_delete_tx(b, user_pk):
from bigchaindb.pipelines.block import BlockPipeline
block_maker = BlockPipeline()
for i in range(100):
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 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_pk):
from bigchaindb.pipelines.block import initial
for i in range(100):
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_pk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private])
b.write_transaction(tx)
@ -167,7 +168,8 @@ def test_full_pipeline(b, user_pk):
count_assigned_to_me = 0
for i in range(100):
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_pk], 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_pk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_pk])
tx1 = Transaction.create([b.me], [([user_pk], 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_pk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_pk])
tx1 = Transaction.create([b.me], [([user_pk], 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_pk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_pk])
tx1 = Transaction.create([b.me], [([user_pk], 1)])
test_block = b.create_block([tx1])
# simulate a federation with four voters
@ -103,7 +103,7 @@ def test_check_requeue_transaction(b, user_pk):
e = election.Election()
# create blocks with transactions
tx1 = Transaction.create([b.me], [user_pk])
tx1 = Transaction.create([b.me], [([user_pk], 1)])
test_block = b.create_block([tx1])
e.requeue_transactions(test_block)
@ -131,7 +131,8 @@ def test_full_pipeline(b, user_pk):
# write two blocks
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_pk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private])
txs.append(tx)
@ -140,7 +141,8 @@ def test_full_pipeline(b, user_pk):
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_pk], 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_pk):
from bigchaindb.models import Transaction
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx, durability='hard')
@ -27,7 +27,7 @@ def test_get_stale(b, user_pk):
def test_reassign_transactions(b, user_pk):
from bigchaindb.models import Transaction
# test with single node
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx, durability='hard')
@ -36,7 +36,7 @@ def test_reassign_transactions(b, user_pk):
stm.reassign_transactions(tx.to_dict())
# test with federation
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 1)])
tx = tx.sign([b.me_private])
b.write_transaction(tx, durability='hard')
@ -51,7 +51,7 @@ def test_reassign_transactions(b, user_pk):
assert reassigned_tx['assignee'] != tx['assignee']
# test with node not in federation
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 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_pk):
monkeypatch.setattr('time.time', lambda: 1)
for i in range(100):
tx = Transaction.create([b.me], [user_pk])
tx = Transaction.create([b.me], [([user_pk], 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_pk):
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_pk):
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_pk):
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

@ -25,7 +25,7 @@ def app(request, node_config):
restore_config(request, node_config)
from bigchaindb.web import server
app = server.create_app({'debug': True})
app = server.create_app(debug=True)
return app

5
tests/web/test_info.py Normal file
View File

@ -0,0 +1,5 @@
def test_api_endpoint_shows_basic_info(client):
from bigchaindb import version
res = client.get('/')
assert res.json['software'] == 'BigchainDB'
assert res.json['version'] == version.__version__

View File

@ -25,18 +25,11 @@ def test_get_transaction_returns_404_if_not_found(client):
assert res.status_code == 404
def test_api_endpoint_shows_basic_info(client):
from bigchaindb import version
res = client.get('/')
assert res.json['software'] == 'BigchainDB'
assert res.json['version'] == version.__version__
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 +41,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 +53,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 +70,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
input_valid = b.get_owned_ids(user_pk).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 +88,8 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk, user_
input_valid = b.get_owned_ids(user_pk).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