Merged master branch and fixed some merge conflicts

This commit is contained in:
troymc 2016-11-14 18:35:46 +01:00
commit 54f0d85cda
38 changed files with 2205 additions and 1038 deletions

View File

@ -16,7 +16,9 @@ install:
- pip install -e .[test]
- pip install codecov
before_script: rethinkdb --daemon
before_script:
- flake8 --max-line-length 119 bigchaindb/
- rethinkdb --daemon
script: py.test -n auto -s -v --cov=bigchaindb

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

@ -82,7 +82,6 @@ def run_configure(args, skip_if_exists=False):
conf,
bigchaindb.config_utils.env_config(bigchaindb.config))
print('Generating keypair', file=sys.stderr)
conf['keypair']['private'], conf['keypair']['public'] = \
crypto.generate_key_pair()
@ -170,7 +169,6 @@ def run_start(args):
else:
logger.warning('Keypair found, no need to create one on the fly.')
if args.start_rethinkdb:
try:
proc = utils.start_rethinkdb()

View File

@ -14,7 +14,6 @@ from bigchaindb import db
from bigchaindb.version import __version__
def start_rethinkdb():
"""Start RethinkDB as a child process and wait for it to be
available.

View File

@ -3,13 +3,13 @@ from functools import reduce
from uuid import uuid4
from cryptoconditions import (Fulfillment as CCFulfillment,
ThresholdSha256Fulfillment, Ed25519Fulfillment,
PreimageSha256Fulfillment)
ThresholdSha256Fulfillment, Ed25519Fulfillment)
from cryptoconditions.exceptions import ParsingError
from bigchaindb.common.crypto import SigningKey, hash_data
from bigchaindb.common.exceptions import (KeypairMismatchException,
InvalidHash, InvalidSignature)
InvalidHash, InvalidSignature,
AmountError, AssetIdMismatch)
from bigchaindb.common.util import serialize, gen_timestamp
@ -38,16 +38,14 @@ class Fulfillment(object):
TransactionLink`, optional): A link representing the input
of a `TRANSFER` Transaction.
"""
self.fulfillment = fulfillment
if tx_input is not None and not isinstance(tx_input, TransactionLink):
raise TypeError('`tx_input` must be a TransactionLink instance')
else:
self.tx_input = tx_input
if not isinstance(owners_before, list):
raise TypeError('`owners_after` must be a list instance')
else:
self.fulfillment = fulfillment
self.tx_input = tx_input
self.owners_before = owners_before
def __eq__(self, other):
@ -98,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.
@ -216,13 +222,12 @@ class Condition(object):
Raises:
TypeError: if `owners_after` is not instance of `list`.
"""
if not isinstance(owners_after, list) and owners_after is not None:
raise TypeError('`owners_after` must be a list instance or None')
self.fulfillment = fulfillment
# TODO: Not sure if we should validate for value here
self.amount = amount
if not isinstance(owners_after, list) and owners_after is not None:
raise TypeError('`owners_after` must be a list instance or None')
else:
self.owners_after = owners_after
def __eq__(self, other):
@ -268,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:
@ -278,34 +283,24 @@ class Condition(object):
[(address|condition)*, [(address|condition)*, ...], ...]
If however, the thresholds of individual threshold conditions
to be created have to be set specifically, a tuple of the
following structure is necessary:
([(address|condition)*,
([(address|condition)*, ...], subthreshold),
...], threshold)
Args:
owners_after (:obj:`list` of :obj:`str`|tuple): The users that
should be able to fulfill the Condition that is being
created.
owners_after (:obj:`list` of :obj:`str`): The public key of
the users that should be able to fulfill the Condition
that is being created.
amount (:obj:`int`): The amount locked by the condition.
Returns:
A Condition that can be used in a Transaction.
Returns:
Raises:
TypeError: If `owners_after` is not an instance of `list`.
TypeError: If `owners_after` is an empty list.
ValueError: If `owners_after` is an empty list.
"""
# TODO: We probably want to remove the tuple logic for weights here
# again:
# github.com/bigchaindb/bigchaindb/issues/730#issuecomment-255144756
if isinstance(owners_after, tuple):
owners_after, threshold = owners_after
else:
threshold = len(owners_after)
if not isinstance(amount, int):
raise TypeError('`amount` must be a int')
if amount < 1:
raise AmountError('`amount` needs to be greater than zero')
if not isinstance(owners_after, list):
raise TypeError('`owners_after` must be an instance of list')
if len(owners_after) == 0:
@ -316,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):
@ -341,9 +336,6 @@ class Condition(object):
Returns:
:class:`cryptoconditions.ThresholdSha256Fulfillment`:
"""
if isinstance(current, tuple):
owners_after, threshold = current
else:
owners_after = current
try:
threshold = len(owners_after)
@ -423,7 +415,7 @@ class Asset(object):
self.updatable = updatable
self.refillable = refillable
self._validate_asset()
self.validate_asset()
def __eq__(self, other):
try:
@ -466,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')
@ -477,6 +500,77 @@ class Asset(object):
if not isinstance(self.updatable, bool):
raise TypeError('`updatable` must be a boolean')
if self.refillable:
raise NotImplementedError('Refillable assets are not yet'
' implemented')
if self.updatable:
raise NotImplementedError('Updatable assets are not yet'
' implemented')
# If the amount is supplied we can perform extra validations to
# the asset
if amount is not None:
if not isinstance(amount, int):
raise TypeError('`amount` must be an int')
if self.divisible is False and amount != 1:
raise AmountError('non divisible assets always have'
' amount equal to one')
# Since refillable assets are not yet implemented this should
# raise and exception
if self.divisible is True and amount < 2:
raise AmountError('divisible assets must have an amount'
' greater than one')
class AssetLink(Asset):
"""An object for unidirectional linking to a Asset.
"""
def __init__(self, data_id=None):
"""Used to point to a specific Asset.
Args:
data_id (str): A Asset to link to.
"""
self.data_id = data_id
def __bool__(self):
return self.data_id is not None
def __eq__(self, other):
return isinstance(other, AssetLink) and \
self.to_dict() == self.to_dict()
@classmethod
def from_dict(cls, link):
"""Transforms a Python dictionary to a AssetLink object.
Args:
link (dict): The link to be transformed.
Returns:
:class:`~bigchaindb.common.transaction.AssetLink`
"""
try:
return cls(link['id'])
except TypeError:
return cls()
def to_dict(self):
"""Transforms the object to a Python dictionary.
Returns:
(dict|None): The link as an alternative serialization format.
"""
if self.data_id is None:
return None
else:
return {
'id': self.data_id
}
class Metadata(object):
"""Metadata is used to store a dictionary and its hash in a Transaction."""
@ -493,15 +587,11 @@ class Metadata(object):
data_id (str): A hash corresponding to the contents of
`data`.
"""
# TODO: Rename `payload_id` to `id`
if data_id is not None:
self.data_id = data_id
else:
self.data_id = self.to_hash()
if data is not None and not isinstance(data, dict):
raise TypeError('`data` must be a dict instance or None')
else:
# TODO: Rename `payload_id` to `id`
self.data_id = data_id if data_id is not None else self.to_hash()
self.data = data
def __eq__(self, other):
@ -592,55 +682,44 @@ class Transaction(object):
version (int): Defines the version number of a Transaction.
"""
if version is not None:
self.version = version
else:
self.version = self.__class__.VERSION
if timestamp is not None:
self.timestamp = timestamp
else:
self.timestamp = gen_timestamp()
if operation not in Transaction.ALLOWED_OPERATIONS:
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
raise ValueError('`operation` must be one of {}'
.format(allowed_ops))
else:
self.operation = operation
# If an asset is not defined in a `CREATE` transaction, create a
# default one.
if asset is None and operation == Transaction.CREATE:
asset = Asset()
if not isinstance(asset, Asset):
# Only assets for 'CREATE' operations can be un-defined.
if (asset and not isinstance(asset, Asset) or
not asset and operation != Transaction.CREATE):
raise TypeError('`asset` must be an Asset instance')
else:
self.asset = asset
if conditions is not None and not isinstance(conditions, list):
if conditions and not isinstance(conditions, list):
raise TypeError('`conditions` must be a list instance or None')
elif conditions is None:
self.conditions = []
else:
self.conditions = conditions
if fulfillments is not None and not isinstance(fulfillments, list):
if fulfillments and not isinstance(fulfillments, list):
raise TypeError('`fulfillments` must be a list instance or None')
elif fulfillments is None:
self.fulfillments = []
else:
self.fulfillments = fulfillments
if metadata is not None and not isinstance(metadata, Metadata):
raise TypeError('`metadata` must be a Metadata instance or None')
else:
self.version = version if version is not None else self.VERSION
self.timestamp = timestamp if timestamp else gen_timestamp()
self.operation = operation
self.asset = asset if asset else Asset()
self.conditions = conditions if conditions else []
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:
@ -648,7 +727,6 @@ class Transaction(object):
use cases:
- Ed25519
- ThresholdSha256
- PreimageSha256.
Additionally, it provides support for the following BigchainDB
use cases:
@ -663,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`
@ -675,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):
@ -769,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)
@ -812,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,
return [
Fulfillment(self.conditions[cid].fulfillment,
self.conditions[cid].owners_after,
TransactionLink(self.id, cid))
inputs.append(ffill)
return inputs
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.
@ -894,13 +936,12 @@ class Transaction(object):
key_pairs = {gen_public_key(SigningKey(private_key)):
SigningKey(private_key) for private_key in private_keys}
zippedIO = enumerate(zip(self.fulfillments, self.conditions))
for index, (fulfillment, condition) in zippedIO:
for index, fulfillment in enumerate(self.fulfillments):
# NOTE: We clone the current transaction but only add the condition
# and fulfillment we're currently working on plus all
# previously signed ones.
tx_partial = Transaction(self.operation, self.asset, [fulfillment],
[condition], self.metadata,
self.conditions, self.metadata,
self.timestamp, self.version)
tx_partial_dict = tx_partial.to_dict()
@ -1057,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)
@ -1075,11 +1115,10 @@ class Transaction(object):
tx_serialized,
input_condition_uri)
if not fulfillments_count == conditions_count == \
input_condition_uris_count:
raise ValueError('Fulfillments, conditions and '
if not fulfillments_count == input_condition_uris_count:
raise ValueError('Fulfillments and '
'input_condition_uris must have the same count')
else:
partial_transactions = map(gen_tx, self.fulfillments,
self.conditions, input_condition_uris)
return all(partial_transactions)
@ -1240,7 +1279,10 @@ class Transaction(object):
conditions = [Condition.from_dict(condition) for condition
in tx['conditions']]
metadata = Metadata.from_dict(tx['metadata'])
if tx['operation'] in [cls.CREATE, cls.GENESIS]:
asset = Asset.from_dict(tx['asset'])
else:
asset = AssetLink.from_dict(tx['asset'])
return cls(tx['operation'], asset, fulfillments, conditions,
metadata, tx['timestamp'], tx_body['version'])

View File

@ -6,13 +6,11 @@ from time import time
from itertools import compress
from bigchaindb.common import crypto, exceptions
from bigchaindb.common.util import gen_timestamp, serialize
from bigchaindb.common.transaction import TransactionLink, Metadata
import rethinkdb as r
from bigchaindb.common.transaction import TransactionLink, Asset
import bigchaindb
from bigchaindb.db.utils import Connection
from bigchaindb.db.utils import Connection, get_backend
from bigchaindb import config_utils, util
from bigchaindb.consensus import BaseConsensusRules
from bigchaindb.models import Block, Transaction
@ -33,7 +31,7 @@ class Bigchain(object):
# return if transaction is in backlog
TX_IN_BACKLOG = 'backlog'
def __init__(self, host=None, port=None, dbname=None,
def __init__(self, host=None, port=None, dbname=None, backend=None,
public_key=None, private_key=None, keyring=[],
backlog_reassign_delay=None):
"""Initialize the Bigchain instance
@ -51,6 +49,8 @@ class Bigchain(object):
host (str): hostname where RethinkDB is running.
port (int): port in which RethinkDB is running (usually 28015).
dbname (str): the name of the database to connect to (usually bigchain).
backend (:class:`~bigchaindb.db.backends.rethinkdb.RehinkDBBackend`):
the database backend to use.
public_key (str): the base58 encoded public key for the ED25519 curve.
private_key (str): the base58 encoded private key for the ED25519 curve.
keyring (list[str]): list of base58 encoded public keys of the federation nodes.
@ -60,6 +60,7 @@ class Bigchain(object):
self.host = host or bigchaindb.config['database']['host']
self.port = port or bigchaindb.config['database']['port']
self.dbname = dbname or bigchaindb.config['database']['name']
self.backend = backend or get_backend(host, port, dbname)
self.me = public_key or bigchaindb.config['keypair']['public']
self.me_private = private_key or bigchaindb.config['keypair']['private']
self.nodes_except_me = keyring or bigchaindb.config['keyring']
@ -73,9 +74,6 @@ class Bigchain(object):
self.connection = Connection(host=self.host, port=self.port, db=self.dbname)
def reconnect(self):
return r.connect(host=self.host, port=self.port, db=self.dbname)
def write_transaction(self, signed_transaction, durability='soft'):
"""Write the transaction to bigchain.
@ -102,12 +100,9 @@ class Bigchain(object):
signed_transaction.update({'assignment_timestamp': time()})
# write to the backlog
response = self.connection.run(
r.table('backlog')
.insert(signed_transaction, durability=durability))
return response
return self.backend.write_transaction(signed_transaction)
def reassign_transaction(self, transaction, durability='hard'):
def reassign_transaction(self, transaction):
"""Assign a transaction to a new node
Args:
@ -131,23 +126,30 @@ class Bigchain(object):
# There is no other node to assign to
new_assignee = self.me
response = self.connection.run(
r.table('backlog')
.get(transaction['id'])
.update({'assignee': new_assignee, 'assignment_timestamp': time()},
durability=durability))
return response
return self.backend.update_transaction(
transaction['id'],
{'assignee': new_assignee, 'assignment_timestamp': time()})
def delete_transaction(self, *transaction_id):
"""Delete a transaction from the backlog.
Args:
*transaction_id (str): the transaction(s) to delete
Returns:
The database response.
"""
return self.backend.delete_transaction(*transaction_id)
def get_stale_transactions(self):
"""Get a RethinkDB cursor of stale transactions
"""Get a cursor of stale transactions.
Transactions are considered stale if they have been assigned a node, but are still in the
backlog after some amount of time specified in the configuration
"""
return self.connection.run(
r.table('backlog')
.filter(lambda tx: time() - tx['assignment_timestamp'] > self.backlog_reassign_delay))
return self.backend.get_stale_transactions(self.backlog_reassign_delay)
def validate_transaction(self, transaction):
"""Validate a transaction.
@ -180,26 +182,55 @@ 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.TransactionNotInValidBlock):
exceptions.TransactionNotInValidBlock, exceptions.AmountError):
return False
def get_transaction(self, txid, include_status=False):
"""Retrieve a transaction with `txid` from bigchain.
def get_block(self, block_id, include_status=False):
"""Get the block with the specified `block_id` (and optionally its status)
Queries the bigchain for a transaction, if it's in a valid or invalid
block.
Returns the block corresponding to `block_id` or None if no match is
found.
Args:
txid (str): transaction id of the transaction to query
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)
This query begins by looking in the bigchain table for all blocks containing
a transaction with the specified `txid`. If one of those blocks is valid, it
returns the matching transaction from that block. Else if some of those
blocks are undecided, it returns a matching transaction from one of them. If
the transaction was found in invalid blocks only, or in no blocks, then this
query looks for a matching transaction in the backlog table, and if it finds
one there, it returns that.
Args:
txid (str): transaction id of the transaction to get
include_status (bool): also return the status of the transaction
the return value is then a tuple: (tx, status)
Returns:
A :class:`~.models.Transaction` instance if the transaction
was found, otherwise ``None``.
was found in a valid block, an undecided block, or the backlog table,
otherwise ``None``.
If :attr:`include_status` is ``True``, also returns the
transaction's status if the transaction was found.
"""
@ -207,6 +238,7 @@ class Bigchain(object):
response, tx_status = None, None
validity = self.get_blocks_status_containing_tx(txid)
check_backlog = True
if validity:
# Disregard invalid blocks, and return if there are no valid or undecided blocks
@ -214,29 +246,25 @@ class Bigchain(object):
if status != Bigchain.BLOCK_INVALID}
if validity:
# The transaction _was_ found in an undecided or valid block,
# so there's no need to look in the backlog table
check_backlog = False
tx_status = self.TX_UNDECIDED
# If the transaction is in a valid or any undecided block, return it. Does not check
# if transactions in undecided blocks are consistent, but selects the valid block before
# undecided ones
# if transactions in undecided blocks are consistent, but selects the valid block
# before undecided ones
for target_block_id in validity:
if validity[target_block_id] == Bigchain.BLOCK_VALID:
tx_status = self.TX_VALID
break
# Query the transaction in the target block and return
response = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get(target_block_id)
.get_field('block')
.get_field('transactions')
.filter(lambda tx: tx['id'] == txid))[0]
response = self.backend.get_transaction_from_block(txid, target_block_id)
if check_backlog:
response = self.backend.get_transaction_from_backlog(txid)
else:
# Otherwise, check the backlog
response = self.connection.run(r.table('backlog')
.get(txid)
.without('assignee', 'assignment_timestamp')
.default(None))
if response:
tx_status = self.TX_IN_BACKLOG
@ -262,24 +290,6 @@ class Bigchain(object):
_, status = self.get_transaction(txid, include_status=True)
return status
def search_block_election_on_index(self, value, index):
"""Retrieve block election information given a secondary index and value
Args:
value: a value to search (e.g. transaction id string, payload hash string)
index (str): name of a secondary index, e.g. 'transaction_id'
Returns:
:obj:`list` of :obj:`dict`: A list of blocks with with only election information
"""
# First, get information on all blocks which contain this transaction
response = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(value, index=index)
.pluck('votes', 'id', {'block': ['voters']}))
return list(response)
def get_blocks_status_containing_tx(self, txid):
"""Retrieve block ids and statuses related to a transaction
@ -294,7 +304,7 @@ class Bigchain(object):
"""
# First, get information on all blocks which contain this transaction
blocks = self.search_block_election_on_index(txid, 'transaction_id')
blocks = self.backend.get_blocks_status_from_transaction(txid)
if blocks:
# Determine the election status of each block
validity = {
@ -336,14 +346,8 @@ class Bigchain(object):
A list of transactions containing that metadata. If no transaction exists with that metadata it
returns an empty list `[]`
"""
cursor = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(metadata_id, index='metadata_id')
.concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['metadata']['id'] == metadata_id))
transactions = list(cursor)
return [Transaction.from_dict(tx) for tx in transactions]
cursor = self.backend.get_transactions_by_metadata_id(metadata_id)
return [Transaction.from_dict(tx) for tx in cursor]
def get_txs_by_asset_id(self, asset_id):
"""Retrieves transactions related to a particular asset.
@ -358,14 +362,25 @@ class Bigchain(object):
A list of transactions containing related to the asset. If no transaction exists for that asset it
returns an empty list `[]`
"""
cursor = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(asset_id, index='asset_id')
.concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id))
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.
@ -382,13 +397,7 @@ class Bigchain(object):
"""
# checks if an input was already spent
# checks if the bigchain has any transaction with input {'txid': ..., 'cid': ...}
response = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['fulfillments']
.contains(lambda fulfillment: fulfillment['input'] == {'txid': txid, 'cid': cid})))
transactions = list(response)
transactions = list(self.backend.get_spent(txid, cid))
# a transaction_id should have been spent at most one time
if transactions:
@ -400,7 +409,8 @@ class Bigchain(object):
if self.get_transaction(transaction['id']):
num_valid_transactions += 1
if num_valid_transactions > 1:
raise exceptions.DoubleSpend('`{}` was spent more then once. There is a problem with the chain'.format(
raise exceptions.DoubleSpend(
'`{}` was spent more then once. There is a problem with the chain'.format(
txid))
if num_valid_transactions:
@ -423,12 +433,7 @@ class Bigchain(object):
"""
# get all transactions in which owner is in the `owners_after` list
response = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda tx: tx['transaction']['conditions']
.contains(lambda c: c['owners_after']
.contains(owner))))
response = self.backend.get_owned_ids(owner)
owned = []
for tx in response:
@ -513,9 +518,7 @@ class Bigchain(object):
but the vote is invalid.
"""
votes = list(self.connection.run(
r.table('votes', read_mode=self.read_mode)
.get_all([block_id, self.me], index='block_and_voter')))
votes = list(self.backend.get_votes_by_block_id_and_voter(block_id, self.me))
if len(votes) > 1:
raise exceptions.MultipleVotesError('Block {block_id} has {n_votes} votes from public key {me}'
@ -537,21 +540,16 @@ class Bigchain(object):
block (Block): block to write to bigchain.
"""
self.connection.run(
r.table('bigchain')
.insert(r.json(block.to_str()), durability=durability))
return self.backend.write_block(block.to_str(), durability=durability)
def transaction_exists(self, transaction_id):
response = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)\
.get_all(transaction_id, index='transaction_id'))
return len(response.items) > 0
return self.backend.has_transaction(transaction_id)
def prepare_genesis_block(self):
"""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
@ -574,9 +572,7 @@ class Bigchain(object):
# 2. create the block with one transaction
# 3. write the block to the bigchain
blocks_count = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.count())
blocks_count = self.backend.count_blocks()
if blocks_count:
raise exceptions.GenesisBlockAlreadyExistsError('Cannot create the Genesis block')
@ -621,69 +617,12 @@ class Bigchain(object):
def write_vote(self, vote):
"""Write the vote to the database."""
self.connection.run(
r.table('votes')
.insert(vote))
return self.backend.write_vote(vote)
def get_last_voted_block(self):
"""Returns the last block that this node voted on."""
try:
# get the latest value for the vote timestamp (over all votes)
max_timestamp = self.connection.run(
r.table('votes', read_mode=self.read_mode)
.filter(r.row['node_pubkey'] == self.me)
.max(r.row['vote']['timestamp']))['vote']['timestamp']
last_voted = list(self.connection.run(
r.table('votes', read_mode=self.read_mode)
.filter(r.row['vote']['timestamp'] == max_timestamp)
.filter(r.row['node_pubkey'] == self.me)))
except r.ReqlNonExistenceError:
# return last vote if last vote exists else return Genesis block
res = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(util.is_genesis_block))
block = list(res)[0]
return Block.from_dict(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
# then we need to rebuild the chain for the blocks that have been retrieved
# to get the last one.
# Given a block_id, mapping returns the id of the block pointing at it.
mapping = {v['vote']['previous_block']: v['vote']['voting_for_block']
for v in last_voted}
# Since we follow the chain backwards, we can start from a random
# point of the chain and "move up" from it.
last_block_id = list(mapping.values())[0]
# We must be sure to break the infinite loop. This happens when:
# - the block we are currenty iterating is the one we are looking for.
# This will trigger a KeyError, breaking the loop
# - we are visiting again a node we already explored, hence there is
# a loop. This might happen if a vote points both `previous_block`
# and `voting_for_block` to the same `block_id`
explored = set()
while True:
try:
if last_block_id in explored:
raise exceptions.CyclicBlockchainError()
explored.add(last_block_id)
last_block_id = mapping[last_block_id]
except KeyError:
break
res = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get(last_block_id))
return Block.from_dict(res)
return Block.from_dict(self.backend.get_last_voted_block(self.me))
def get_unvoted_blocks(self):
"""Return all the blocks that have not been voted on by this node.
@ -692,37 +631,26 @@ class Bigchain(object):
:obj:`list` of :obj:`dict`: a list of unvoted blocks
"""
unvoted = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(lambda block: r.table('votes', read_mode=self.read_mode)
.get_all([block['id'], self.me], index='block_and_voter')
.is_empty())
.order_by(r.asc(r.row['block']['timestamp'])))
# FIXME: I (@vrde) don't like this solution. Filtering should be done at a
# database level. Solving issue #444 can help untangling the situation
unvoted_blocks = filter(lambda block: not util.is_genesis_block(block), unvoted)
return unvoted_blocks
# XXX: should this return instaces of Block?
return self.backend.get_unvoted_blocks(self.me)
def block_election_status(self, block_id, voters):
"""Tally the votes on a block, and return the status: valid, invalid, or undecided."""
votes = self.connection.run(r.table('votes', read_mode=self.read_mode)
.between([block_id, r.minval], [block_id, r.maxval], index='block_and_voter'))
votes = list(votes)
votes = list(self.backend.get_votes_by_block_id(block_id))
n_voters = len(voters)
voter_counts = collections.Counter([vote['node_pubkey'] for vote in votes])
for node in voter_counts:
if voter_counts[node] > 1:
raise exceptions.MultipleVotesError('Block {block_id} has multiple votes ({n_votes}) from voting node {node_id}'
raise exceptions.MultipleVotesError(
'Block {block_id} has multiple votes ({n_votes}) from voting node {node_id}'
.format(block_id=block_id, n_votes=str(voter_counts[node]), node_id=node))
if len(votes) > n_voters:
raise exceptions.MultipleVotesError('Block {block_id} has {n_votes} votes cast, but only {n_voters} voters'
.format(block_id=block_id, n_votes=str(len(votes)), n_voters=str(n_voters)))
.format(block_id=block_id, n_votes=str(len(votes)),
n_voters=str(n_voters)))
# vote_cast is the list of votes e.g. [True, True, False]
vote_cast = [vote['vote']['is_block_valid'] for vote in votes]

View File

@ -1,2 +1,2 @@
# TODO can we use explicit imports?
from bigchaindb.db.utils import *
from bigchaindb.db.utils import * # noqa: F401,F403

View File

View File

@ -0,0 +1,431 @@
"""Backend implementation for RethinkDB.
This module contains all the methods to store and retrieve data from RethinkDB.
"""
from time import time
import rethinkdb as r
from bigchaindb import util
from bigchaindb.db.utils import Connection
from bigchaindb.common import exceptions
class RethinkDBBackend:
def __init__(self, host=None, port=None, db=None):
"""Initialize a new RethinkDB Backend instance.
Args:
host (str): the host to connect to.
port (int): the port to connect to.
db (str): the name of the database to use.
"""
self.read_mode = 'majority'
self.durability = 'soft'
self.connection = Connection(host=host, port=port, db=db)
def write_transaction(self, signed_transaction):
"""Write a transaction to the backlog table.
Args:
signed_transaction (dict): a signed transaction.
Returns:
The result of the operation.
"""
return self.connection.run(
r.table('backlog')
.insert(signed_transaction, durability=self.durability))
def update_transaction(self, transaction_id, doc):
"""Update a transaction in the backlog table.
Args:
transaction_id (str): the id of the transaction.
doc (dict): the values to update.
Returns:
The result of the operation.
"""
return self.connection.run(
r.table('backlog')
.get(transaction_id)
.update(doc))
def delete_transaction(self, *transaction_id):
"""Delete a transaction from the backlog.
Args:
*transaction_id (str): the transaction(s) to delete
Returns:
The database response.
"""
return self.connection.run(
r.table('backlog')
.get_all(*transaction_id)
.delete(durability='hard'))
def get_stale_transactions(self, reassign_delay):
"""Get a cursor of stale transactions.
Transactions are considered stale if they have been assigned a node,
but are still in the backlog after some amount of time specified in the
configuration.
Args:
reassign_delay (int): threshold (in seconds) to mark a transaction stale.
Returns:
A cursor of transactions.
"""
return self.connection.run(
r.table('backlog')
.filter(lambda tx: time() - tx['assignment_timestamp'] > reassign_delay))
def get_transaction_from_block(self, transaction_id, block_id):
"""Get a transaction from a specific block.
Args:
transaction_id (str): the id of the transaction.
block_id (str): the id of the block.
Returns:
The matching transaction.
"""
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get(block_id)
.get_field('block')
.get_field('transactions')
.filter(lambda tx: tx['id'] == transaction_id))[0]
def get_transaction_from_backlog(self, transaction_id):
"""Get a transaction from backlog.
Args:
transaction_id (str): the id of the transaction.
Returns:
The matching transaction.
"""
return self.connection.run(
r.table('backlog')
.get(transaction_id)
.without('assignee', 'assignment_timestamp')
.default(None))
def get_blocks_status_from_transaction(self, transaction_id):
"""Retrieve block election information given a secondary index and value
Args:
value: a value to search (e.g. transaction id string, payload hash string)
index (str): name of a secondary index, e.g. 'transaction_id'
Returns:
:obj:`list` of :obj:`dict`: A list of blocks with with only election information
"""
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(transaction_id, index='transaction_id')
.pluck('votes', 'id', {'block': ['voters']}))
def get_transactions_by_metadata_id(self, metadata_id):
"""Retrieves transactions related to a metadata.
When creating a transaction one of the optional arguments is the `metadata`. The metadata is a generic
dict that contains extra information that can be appended to the transaction.
To make it easy to query the bigchain for that particular metadata we create a UUID for the metadata and
store it with the transaction.
Args:
metadata_id (str): the id for this particular metadata.
Returns:
A list of transactions containing that metadata. If no transaction exists with that metadata it
returns an empty list `[]`
"""
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(metadata_id, index='metadata_id')
.concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['metadata']['id'] == metadata_id))
def get_transactions_by_asset_id(self, asset_id):
"""Retrieves transactions related to a particular asset.
A digital asset in bigchaindb is identified by an uuid. This allows us to query all the transactions
related to a particular digital asset, knowing the id.
Args:
asset_id (str): the id for this particular metadata.
Returns:
A list of transactions containing related to the asset. If no transaction exists for that asset it
returns an empty list `[]`
"""
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))
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.
A transaction can be used as an input for another transaction. Bigchain needs to make sure that a
given `txid` is only used once.
Args:
transaction_id (str): The id of the transaction.
condition_id (int): The index of the condition in the respective transaction.
Returns:
The transaction that used the `txid` as an input else `None`
"""
# TODO: use index!
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['fulfillments'].contains(
lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id})))
def get_owned_ids(self, owner):
"""Retrieve a list of `txids` that can we used has inputs.
Args:
owner (str): base58 encoded public key.
Returns:
A cursor for the matching transactions.
"""
# TODO: use index!
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda tx: tx['transaction']['conditions'].contains(
lambda c: c['owners_after'].contains(owner))))
def get_votes_by_block_id(self, block_id):
"""Get all the votes casted for a specific block.
Args:
block_id (str): the block id to use.
Returns:
A cursor for the matching votes.
"""
return self.connection.run(
r.table('votes', read_mode=self.read_mode)
.between([block_id, r.minval], [block_id, r.maxval], index='block_and_voter'))
def get_votes_by_block_id_and_voter(self, block_id, node_pubkey):
"""Get all the votes casted for a specific block by a specific voter.
Args:
block_id (str): the block id to use.
node_pubkey (str): base58 encoded public key
Returns:
A cursor for the matching votes.
"""
return self.connection.run(
r.table('votes', read_mode=self.read_mode)
.get_all([block_id, node_pubkey], index='block_and_voter'))
def write_block(self, block, durability='soft'):
"""Write a block to the bigchain table.
Args:
block (dict): the block to write.
Returns:
The database response.
"""
return self.connection.run(
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.
Args:
transaction_id (str): the id of the transaction to check.
Returns:
``True`` if the transaction exists, ``False`` otherwise.
"""
return bool(self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get_all(transaction_id, index='transaction_id').count()))
def count_blocks(self):
"""Count the number of blocks in the bigchain table.
Returns:
The number of blocks.
"""
return self.connection.run(
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.
Args:
vote (dict): the vote to write.
Returns:
The database response.
"""
return self.connection.run(
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.
Args:
node_pubkey (str): base58 encoded public key.
Returns:
The last block the node has voted on. If the node didn't cast
any vote then the genesis block is returned.
"""
try:
# get the latest value for the vote timestamp (over all votes)
max_timestamp = self.connection.run(
r.table('votes', read_mode=self.read_mode)
.filter(r.row['node_pubkey'] == node_pubkey)
.max(r.row['vote']['timestamp']))['vote']['timestamp']
last_voted = list(self.connection.run(
r.table('votes', read_mode=self.read_mode)
.filter(r.row['vote']['timestamp'] == max_timestamp)
.filter(r.row['node_pubkey'] == node_pubkey)))
except r.ReqlNonExistenceError:
# return last vote if last vote exists else return Genesis block
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
# then we need to rebuild the chain for the blocks that have been retrieved
# to get the last one.
# Given a block_id, mapping returns the id of the block pointing at it.
mapping = {v['vote']['previous_block']: v['vote']['voting_for_block']
for v in last_voted}
# Since we follow the chain backwards, we can start from a random
# point of the chain and "move up" from it.
last_block_id = list(mapping.values())[0]
# We must be sure to break the infinite loop. This happens when:
# - the block we are currenty iterating is the one we are looking for.
# This will trigger a KeyError, breaking the loop
# - we are visiting again a node we already explored, hence there is
# a loop. This might happen if a vote points both `previous_block`
# and `voting_for_block` to the same `block_id`
explored = set()
while True:
try:
if last_block_id in explored:
raise exceptions.CyclicBlockchainError()
explored.add(last_block_id)
last_block_id = mapping[last_block_id]
except KeyError:
break
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.get(last_block_id))
def get_unvoted_blocks(self, node_pubkey):
"""Return all the blocks that have not been voted by the specified node.
Args:
node_pubkey (str): base58 encoded public key
Returns:
:obj:`list` of :obj:`dict`: a list of unvoted blocks
"""
unvoted = self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(lambda block: r.table('votes', read_mode=self.read_mode)
.get_all([block['id'], node_pubkey], index='block_and_voter')
.is_empty())
.order_by(r.asc(r.row['block']['timestamp'])))
# FIXME: I (@vrde) don't like this solution. Filtering should be done at a
# database level. Solving issue #444 can help untangling the situation
unvoted_blocks = filter(lambda block: not util.is_genesis_block(block), unvoted)
return unvoted_blocks

View File

@ -67,6 +67,18 @@ class Connection:
time.sleep(2**i)
def get_backend(host=None, port=None, db=None):
'''Get a backend instance.'''
from bigchaindb.db.backends import rethinkdb
# NOTE: this function will be re-implemented when we have real
# multiple backends to support. Right now it returns the RethinkDB one.
return rethinkdb.RethinkDBBackend(host=host or bigchaindb.config['database']['host'],
port=port or bigchaindb.config['database']['port'],
db=db or bigchaindb.config['database']['name'])
def get_conn():
'''Get the connection to the database.'''
@ -149,10 +161,7 @@ def create_votes_secondary_index(conn, dbname):
r.db(dbname).table('votes').index_wait().run(conn)
def init():
# Try to access the keypair, throws an exception if it does not exist
b = bigchaindb.Bigchain()
def init_database():
conn = get_conn()
dbname = get_database_name()
create_database(conn, dbname)
@ -160,10 +169,18 @@ def init():
table_names = ['bigchain', 'backlog', 'votes']
for table_name in table_names:
create_table(conn, dbname, table_name)
create_bigchain_secondary_index(conn, dbname)
create_backlog_secondary_index(conn, dbname)
create_votes_secondary_index(conn, dbname)
def init():
# Try to access the keypair, throws an exception if it does not exist
b = bigchaindb.Bigchain()
init_database()
logger.info('Create genesis block.')
b.create_genesis_block()
logger.info('Done, have fun!')
@ -172,9 +189,9 @@ def init():
def drop(assume_yes=False):
conn = get_conn()
dbname = bigchaindb.config['database']['name']
if assume_yes:
response = 'y'
else:
response = input('Do you want to drop `{}` database? [y/n]: '.format(dbname))
@ -185,5 +202,6 @@ def drop(assume_yes=False):
logger.info('Done.')
except r.ReqlOpFailedError:
raise exceptions.DatabaseDoesNotExist('Database `{}` does not exist'.format(dbname))
else:
logger.info('Drop aborted')

View File

@ -3,41 +3,11 @@ from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
OperationError, DoubleSpend,
TransactionDoesNotExist,
TransactionNotInValidBlock,
AssetIdMismatch)
AssetIdMismatch, AmountError)
from bigchaindb.common.transaction import Transaction, Asset
from bigchaindb.common.util import gen_timestamp, serialize
class Asset(Asset):
@staticmethod
def get_asset_id(transactions):
"""Get the asset id from a list of transaction ids.
This is useful when we want to check if the multiple inputs of a transaction
are related to the same asset id.
Args:
transactions (list): list of transaction usually inputs that should have a matching asset_id
Returns:
str: uuid of the asset.
Raises:
AssetIdMismatch: If the inputs are related to different assets.
"""
if not isinstance(transactions, list):
transactions = [transactions]
# create a set of asset_ids
asset_ids = {tx.asset.data_id for tx in transactions}
# check that all the transasctions have the same asset_id
if len(asset_ids) > 1:
raise AssetIdMismatch("All inputs of a transaction need to have the same asset id.")
return asset_ids.pop()
class Transaction(Transaction):
def validate(self, bigchain):
"""Validate a transaction.
@ -73,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 '
@ -81,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
@ -103,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 {}.'
@ -120,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:
@ -148,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
@ -184,6 +209,15 @@ class Block(object):
return self
def sign(self, signing_key):
"""Create a signature for the Block and overwrite `self.signature`.
Args:
signing_key (str): A signing key corresponding to
`self.node_pubkey`.
Returns:
:class:`~.Block`
"""
block_body = self.to_dict()
block_serialized = serialize(block_body['block'])
signing_key = SigningKey(signing_key)
@ -191,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()
verifying_key = VerifyingKey(block['node_pubkey'])
try:
@ -204,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)
@ -239,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

@ -69,10 +69,7 @@ class BlockPipeline:
# if the tx is already in a valid or undecided block,
# then it no longer should be in the backlog, or added
# to a new block. We can delete and drop it.
self.bigchain.connection.run(
r.table('backlog')
.get(tx.id)
.delete(durability='hard'))
self.bigchain.delete_transaction(tx.id)
return None
tx_validated = self.bigchain.is_valid_transaction(tx)
@ -81,10 +78,7 @@ class BlockPipeline:
else:
# if the transaction is not valid, remove it from the
# backlog
self.bigchain.connection.run(
r.table('backlog')
.get(tx.id)
.delete(durability='hard'))
self.bigchain.delete_transaction(tx.id)
return None
def create(self, tx, timeout=False):
@ -136,10 +130,7 @@ class BlockPipeline:
Returns:
:class:`~bigchaindb.models.Block`: The block.
"""
self.bigchain.connection.run(
r.table('backlog')
.get_all(*[tx.id for tx in block.transactions])
.delete(durability='hard'))
self.bigchain.delete_transaction(*[tx.id for tx in block.transactions])
return block

View File

@ -73,4 +73,3 @@ class ChangeFeed(Node):
self.outqueue.put(change['old_val'])
elif is_update and (self.operation & ChangeFeed.UPDATE):
self.outqueue.put(change['new_val'])

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

@ -12,4 +12,3 @@ def make_error(status_code, message=None):
})
response.status_code = status_code
return response

View File

@ -44,9 +44,8 @@ USE_KEYPAIRS_FILE=False
# and you can search for one that meets your needs at:
# https://cloud-images.ubuntu.com/locator/ec2/
# Example:
# "ami-72c33e1d"
# (eu-central-1 Ubuntu 14.04 LTS amd64 hvm:ebs-ssd 20160919)
IMAGE_ID="ami-72c33e1d"
# (eu-central-1 Ubuntu 14.04 LTS amd64 hvm:ebs-ssd 20161020)
IMAGE_ID="ami-9c09f0f3"
# INSTANCE_TYPE is the type of AWS instance to launch
# i.e. How many CPUs do you want? How much storage? etc.

View File

@ -28,11 +28,11 @@ Here's some explanation of the contents of a transaction:
- `fulfillments`: List of fulfillments. Each _fulfillment_ contains a pointer to an unspent asset
and a _crypto fulfillment_ that satisfies a spending condition set on the unspent asset. A _fulfillment_
is usually a signature proving the ownership of the asset.
See [Conditions and Fulfillments](#conditions-and-fulfillments) below.
See the page about [Crypto-Conditions and Fulfillments](crypto-conditions.html).
- `conditions`: List of conditions. Each _condition_ is a _crypto-condition_ that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners.
See [Conditions and Fulfillments](#conditions-and-fulfillments) below.
See the page about [Crypto-Conditions and Fulfillments](crypto-conditions.html).
- `operation`: String representation of the operation being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated.
- `timestamp`: The Unix time when the transaction was created. It's provided by the client. See [the section on timestamps](timestamps.html).
- `timestamp`: The Unix time when the transaction was created. It's provided by the client. See the page about [timestamps in BigchainDB](../timestamps.html).
- `asset`: Definition of the digital asset. See next section.
- `metadata`:
- `id`: UUID version 4 (random) converted to a string of hex digits in standard form.

View File

@ -12,11 +12,18 @@ A creation transaction also establishes the conditions that must be met to trans
A _transfer transaction_ can transfer an asset by fulfilling the current conditions on the asset. It can also specify new transfer conditions.
Today, every transaction contains one fulfillment-condition pair. The fulfillment in a transfer transaction must correspond to a condition in a previous transaction.
Today, every transaction contains one fulfillment-condition pair. The fulfillment in a transfer transaction must fulfill a condition in a previous transaction.
When a node is asked to check the validity of a transaction, it must do several things, including:
When a node is asked to check if a transaction is valid, it checks several things. Some things it checks are:
* double-spending checks (for transfer transactions),
* hash validation (i.e. is the calculated transaction hash equal to its id?), and
* validation of all fulfillments, including validation of cryptographic signatures if theyre among the conditions.
* Are all the fulfillments valid? (Do they correctly satisfy the conditions they claim to satisfy?)
* If it's a creation transaction, is the asset valid?
* If it's a transfer transaction:
* Is it trying to fulfill a condition in a nonexistent transaction?
* Is it trying to fulfill a condition that's not in a valid transaction? (It's okay if the condition is in a transaction in an invalid block; those transactions are ignored. Transactions in the backlog or undecided blocks are not ignored.)
* Is it trying to fulfill a condition that has already been fulfilled, or that some other pending transaction (in the backlog or an undecided block) also aims to fulfill?
* Is the asset ID in the transaction the same as the asset ID in all transactions whose conditions are being fulfilled?
If you're curious about the details of transaction validation, the code is in the `validate` method of the `Transaction` class, in `bigchaindb/models.py` (at the time of writing).
Note: The check to see if the transaction ID is equal to the hash of the transaction body is actually done whenever the transaction is converted from a Python dict to a Transaction object, which must be done before the `validate` method can be called (since it's called on a Transaction object).

View File

@ -126,7 +126,7 @@ BRANCH="master"
WHAT_TO_DEPLOY="servers"
SSH_KEY_NAME="not-set-yet"
USE_KEYPAIRS_FILE=False
IMAGE_ID="ami-72c33e1d"
IMAGE_ID="ami-9c09f0f3"
INSTANCE_TYPE="t2.medium"
SECURITY_GROUP="bigchaindb"
USING_EBS=True

View File

@ -41,7 +41,7 @@ extensions = [
'sphinx.ext.intersphinx',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinxcontrib.napoleon',
'sphinx.ext.napoleon',
'sphinxcontrib.httpdomain',
]

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
@ -40,7 +54,12 @@ 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

View File

@ -31,10 +31,10 @@ check_setuptools_features()
tests_require = [
'coverage',
'pep8',
'pyflakes',
'flake8',
'pylint',
'pytest',
'pytest-cov==2.2.1',
'pytest-cov>=2.2.1',
'pytest-xdist',
'pytest-flask',
]
@ -48,7 +48,6 @@ docs_require = [
'Sphinx>=1.3.5',
'recommonmark>=0.4.0',
'sphinx-rtd-theme>=0.1.9',
'sphinxcontrib-napoleon>=0.4.4',
'sphinxcontrib-httpdomain>=1.5.0',
]
@ -56,6 +55,20 @@ benchmarks_require = [
'line-profiler==1.0',
]
install_requires = [
'rethinkdb~=2.3', # i.e. a version between 2.3 and 3.0
'pysha3>=0.3',
'cryptoconditions>=0.5.0',
'statsd>=3.2.1',
'python-rapidjson>=0.0.6',
'logstats>=0.2.1',
'flask>=0.10.1',
'flask-restful~=0.3.0',
'requests~=2.9',
'gunicorn~=19.0',
'multipipes~=0.1.0',
]
setup(
name='BigchainDB',
version=version['__version__'],
@ -75,7 +88,7 @@ setup(
'Topic :: Software Development',
'Natural Language :: English',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Operating System :: MacOS :: MacOS X',
@ -89,21 +102,7 @@ setup(
'bigchaindb=bigchaindb.commands.bigchain:main'
],
},
install_requires=[
'rethinkdb~=2.3',
'pysha3==0.3',
'pytz==2015.7',
'cryptoconditions==0.5.0',
'statsd==3.2.1',
'python-rapidjson==0.0.6',
'logstats==0.2.1',
'base58==0.2.2',
'flask==0.10.1',
'flask-restful~=0.3.0',
'requests~=2.9',
'gunicorn~=19.0',
'multipipes~=0.1.0',
],
install_requires=install_requires,
setup_requires=['pytest-runner'],
tests_require=tests_require,
extras_require={

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,11 +10,13 @@ import pytest
import rethinkdb as r
from bigchaindb import Bigchain
from bigchaindb.db import get_conn
from bigchaindb.db import get_conn, init_database
from bigchaindb.common import crypto
from bigchaindb.common.exceptions import DatabaseAlreadyExists
USER2_SK, USER2_VK = crypto.generate_key_pair()
@pytest.fixture(autouse=True)
def restore_config(request, node_config):
from bigchaindb import config_utils
@ -25,53 +27,17 @@ def restore_config(request, node_config):
def setup_database(request, node_config):
print('Initializing test db')
db_name = node_config['database']['name']
get_conn().repl()
conn = get_conn()
if r.db_list().contains(db_name).run(conn):
r.db_drop(db_name).run(conn)
try:
r.db_create(db_name).run()
except r.ReqlOpFailedError as e:
if e.message == 'Database `{}` already exists.'.format(db_name):
r.db_drop(db_name).run()
r.db_create(db_name).run()
else:
raise
init_database()
except DatabaseAlreadyExists:
print('Database already exists.')
print('Finished initializing test db')
# setup tables
r.db(db_name).table_create('bigchain').run()
r.db(db_name).table_create('backlog').run()
r.db(db_name).table_create('votes').run()
# create the secondary indexes
# to order blocks by timestamp
r.db(db_name).table('bigchain').index_create('block_timestamp', r.row['block']['timestamp']).run()
# to order blocks by block number
r.db(db_name).table('bigchain').index_create('block_number', r.row['block']['block_number']).run()
# to order transactions by timestamp
r.db(db_name).table('backlog').index_create('transaction_timestamp', r.row['transaction']['timestamp']).run()
# to query by payload uuid
r.db(db_name).table('bigchain').index_create(
'metadata_id',
r.row['block']['transactions']['transaction']['metadata']['id'],
multi=True,
).run()
# compound index to read transactions from the backlog per assignee
r.db(db_name).table('backlog')\
.index_create('assignee__transaction_timestamp', [r.row['assignee'], r.row['transaction']['timestamp']])\
.run()
# compound index to order votes by block id and node
r.db(db_name).table('votes').index_create('block_and_voter',
[r.row['vote']['voting_for_block'], r.row['node_pubkey']]).run()
# secondary index for asset uuid
r.db(db_name).table('bigchain')\
.index_create('asset_id',
r.row['block']['transactions']['transaction']['asset']['id'], multi=True)\
.run()
# order transactions by id
r.db(db_name).table('bigchain').index_create('transaction_id', r.row['block']['transactions']['id'],
multi=True).run()
r.db(db_name).table('bigchain').index_wait('transaction_id').run()
print('Finishing init database')
def fin():
print('Deleting `{}` database'.format(db_name))
@ -81,7 +47,6 @@ def setup_database(request, node_config):
except r.ReqlOpFailedError as e:
if e.message != 'Database `{}` does not exist.'.format(db_name):
raise
print('Finished deleting `{}`'.format(db_name))
request.addfinalizer(fin)
@ -119,7 +84,7 @@ def inputs(user_vk):
prev_block_id = g.id
for block in range(4):
transactions = [
Transaction.create([b.me], [user_vk]).sign([b.me_private])
Transaction.create([b.me], [([user_vk], 1)]).sign([b.me_private])
for i in range(10)
]
block = b.create_block(transactions)

View File

@ -15,7 +15,7 @@ def dummy_tx():
import bigchaindb
from bigchaindb.models import Transaction
b = bigchaindb.Bigchain()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
return tx
@ -37,7 +37,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
block1 = b.create_block([tx])
@ -60,7 +60,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
block1 = b.create_block([tx])
@ -74,7 +74,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -99,7 +99,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -107,13 +107,15 @@ class TestBigchainApi(object):
b.write_block(block1, durability='hard')
monkeypatch.setattr('time.time', lambda: 2)
transfer_tx = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset)
transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
tx.asset)
transfer_tx = transfer_tx.sign([b.me_private])
block2 = b.create_block([transfer_tx])
b.write_block(block2, durability='hard')
monkeypatch.setattr('time.time', lambda: 3)
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset)
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
tx.asset)
transfer_tx2 = transfer_tx2.sign([b.me_private])
block3 = b.create_block([transfer_tx2])
b.write_block(block3, durability='hard')
@ -133,7 +135,7 @@ class TestBigchainApi(object):
b.create_genesis_block()
tx = Transaction.create([b.me], [b.me])
tx = Transaction.create([b.me], [([b.me], 1)])
tx = tx.sign([b.me_private])
monkeypatch.setattr('time.time', lambda: 1)
@ -159,13 +161,13 @@ class TestBigchainApi(object):
b.create_genesis_block()
monkeypatch.setattr('time.time', lambda: 1)
tx1 = Transaction.create([b.me], [b.me])
tx1 = Transaction.create([b.me], [([b.me], 1)])
tx1 = tx1.sign([b.me_private])
block1 = b.create_block([tx1])
b.write_block(block1, durability='hard')
monkeypatch.setattr('time.time', lambda: 2)
tx2 = Transaction.create([b.me], [b.me])
tx2 = Transaction.create([b.me], [([b.me], 1)])
tx2 = tx2.sign([b.me_private])
block2 = b.create_block([tx2])
b.write_block(block2, durability='hard')
@ -185,7 +187,7 @@ class TestBigchainApi(object):
from bigchaindb.models import Transaction
metadata = {'msg': 'Hello BigchainDB!'}
tx = Transaction.create([b.me], [user_vk], metadata=metadata)
tx = Transaction.create([b.me], [([user_vk], 1)], metadata=metadata)
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -205,7 +207,7 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
response = b.write_transaction(tx)
@ -223,7 +225,7 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -243,9 +245,9 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
# There's no need to b.write_transaction(tx) to the backlog
# create block
block = b.create_block([tx])
@ -257,8 +259,37 @@ class TestBigchainApi(object):
response = b.get_transaction(tx.id)
# should be None, because invalid blocks are ignored
# and a copy of the tx is not in the backlog
assert response is None
@pytest.mark.usefixtures('inputs')
def test_read_transaction_invalid_block_and_backlog(self, b, user_vk, user_sk):
from bigchaindb.models import Transaction
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# Make sure there's a copy of tx in the backlog
b.write_transaction(tx)
# create block
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote the block invalid
vote = b.vote(block.id, b.get_last_voted_block().id, False)
b.write_vote(vote)
# a copy of the tx is both in the backlog and in an invalid
# block, so get_transaction should return a transaction,
# and a status of TX_IN_BACKLOG
response, status = b.get_transaction(tx.id, include_status=True)
assert tx.to_dict() == response.to_dict()
assert status == b.TX_IN_BACKLOG
@pytest.mark.usefixtures('inputs')
def test_genesis_block(self, b):
import rethinkdb as r
@ -340,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
@ -499,7 +539,7 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -525,7 +565,7 @@ class TestBigchainApi(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
b.write_transaction(tx)
@ -549,11 +589,21 @@ class TestBigchainApi(object):
fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_vk),
[user_vk],
TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([fulfillment], [user_vk], Asset())
tx = Transaction.transfer([fulfillment], [([user_vk], 1)], Asset())
with pytest.raises(TransactionDoesNotExist) as excinfo:
with pytest.raises(TransactionDoesNotExist):
tx.validate(Bigchain())
def test_count_backlog(self, b, user_vk):
from bigchaindb.models import Transaction
for _ in range(4):
tx = Transaction.create([b.me],
[([user_vk], 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_vk, create_tx):
@ -591,7 +641,7 @@ class TestTransactionValidation(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_transaction = b.get_transaction(input_tx.txid)
sk, vk = generate_key_pair()
tx = Transaction.create([vk], [user_vk])
tx = Transaction.create([vk], [([user_vk], 1)])
tx.operation = 'TRANSFER'
tx.asset = input_transaction.asset
tx.fulfillments[0].tx_input = input_tx
@ -635,7 +685,8 @@ class TestTransactionValidation(object):
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
transfer_tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user_vk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
@ -659,7 +710,8 @@ class TestTransactionValidation(object):
inputs = input_tx.to_inputs()
# create a transaction that's valid but not in a voted valid block
transfer_tx = Transaction.transfer(inputs, [user_vk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user_vk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
@ -669,7 +721,8 @@ class TestTransactionValidation(object):
b.write_block(block, durability='hard')
# create transaction with the undecided input
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [user_vk],
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(),
[([user_vk], 1)],
transfer_tx.asset)
tx_invalid = tx_invalid.sign([user_sk])
@ -768,7 +821,7 @@ class TestMultipleInputs(object):
tx_link = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(tx_link.txid)
inputs = input_tx.to_inputs()
tx = Transaction.transfer(inputs, [user2_vk], input_tx.asset)
tx = Transaction.transfer(inputs, [([user2_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
# validate transaction
@ -776,69 +829,6 @@ class TestMultipleInputs(object):
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs')
def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# get inputs
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user_vk]])
tx = tx.sign([user_sk])
assert b.validate_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs')
def test_transfer_single_owners_single_input_from_multiple_outputs(self, b,
user_sk,
user_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# get inputs
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
# create block with the transaction
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# get inputs from user2
owned_inputs = b.get_owned_ids(user2_vk)
assert len(owned_inputs) == len(inputs)
# create a transaction with a single input from a multiple output transaction
tx_link = owned_inputs.pop()
inputs = b.get_transaction(tx_link.txid).to_inputs([0])
tx = Transaction.transfer(inputs, [user_vk])
tx = tx.sign([user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
def test_single_owner_before_multiple_owners_after_single_input(self, b,
user_sk,
user_vk,
@ -852,47 +842,14 @@ class TestMultipleInputs(object):
owned_inputs = b.get_owned_ids(user_vk)
tx_link = owned_inputs.pop()
input_tx = b.get_transaction(tx_link.txid)
tx = Transaction.transfer(input_tx.to_inputs(), [[user2_vk, user3_vk]], input_tx.asset)
tx = Transaction.transfer(input_tx.to_inputs(),
[([user2_vk, user3_vk], 1)], input_tx.asset)
tx = tx.sign([user_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs')
def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b,
user_sk,
user_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk, user3_vk]])
tx = tx.sign([user_sk])
# create block with the transaction
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# validate transaction
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_single_owner_after_single_input(self, b,
user_sk,
@ -903,7 +860,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -916,7 +873,8 @@ class TestMultipleInputs(object):
input_tx = b.get_transaction(owned_input.txid)
inputs = input_tx.to_inputs()
transfer_tx = Transaction.transfer(inputs, [user3_vk], input_tx.asset)
transfer_tx = Transaction.transfer(inputs, [([user3_vk], 1)],
input_tx.asset)
transfer_tx = transfer_tx.sign([user_sk, user2_sk])
# validate transaction
@ -924,29 +882,6 @@ class TestMultipleInputs(object):
assert len(transfer_tx.fulfillments) == 1
assert len(transfer_tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs_shared')
def test_multiple_owners_before_single_owner_after_multiple_inputs(self, b,
user_sk, user_vk, user2_vk, user2_sk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
# create a new users
user3_sk, user3_vk = crypto.generate_key_pair()
tx_links = b.get_owned_ids(user_vk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user3_vk]])
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_multiple_owners_after_single_input(self, b,
user_sk,
@ -958,7 +893,7 @@ class TestMultipleInputs(object):
user3_sk, user3_vk = crypto.generate_key_pair()
user4_sk, user4_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -971,38 +906,14 @@ class TestMultipleInputs(object):
tx_link = b.get_owned_ids(user_vk).pop()
tx_input = b.get_transaction(tx_link.txid)
tx = Transaction.transfer(tx_input.to_inputs(), [[user3_vk, user4_vk]], tx_input.asset)
tx = Transaction.transfer(tx_input.to_inputs(),
[([user3_vk, user4_vk], 1)], tx_input.asset)
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
@pytest.mark.usefixtures('inputs_shared')
def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self, b,
user_sk, user_vk,
user2_sk, user2_vk):
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
# create a new users
user3_sk, user3_vk = crypto.generate_key_pair()
user4_sk, user4_vk = crypto.generate_key_pair()
tx_links = b.get_owned_ids(user_vk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user3_vk, user4_vk]])
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_vk):
from bigchaindb.common import crypto
from bigchaindb.common.transaction import TransactionLink
@ -1010,7 +921,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1020,7 +931,7 @@ class TestMultipleInputs(object):
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)]
assert owned_inputs_user2 == []
tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1040,7 +951,7 @@ class TestMultipleInputs(object):
genesis = b.create_genesis_block()
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1056,7 +967,8 @@ class TestMultipleInputs(object):
# NOTE: The transaction itself is valid, still will mark the block
# as invalid to mock the behavior.
tx_invalid = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx_invalid = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)],
tx.asset)
tx_invalid = tx_invalid.sign([user_sk])
block = b.create_block([tx_invalid])
b.write_block(block, durability='hard')
@ -1072,47 +984,45 @@ class TestMultipleInputs(object):
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)]
assert owned_inputs_user2 == []
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk,
user_vk):
import random
from bigchaindb.common import crypto
from bigchaindb.common.transaction import TransactionLink
from bigchaindb.common.transaction import TransactionLink, Asset
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
transactions = []
for i in range(2):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_vk], payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
# create divisible asset
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 1), ([user_vk], 1)],
asset=asset)
tx_create_signed = tx_create.sign([b.me_private])
block = b.create_block([tx_create_signed])
b.write_block(block, durability='hard')
# get input
owned_inputs_user1 = b.get_owned_ids(user_vk)
owned_inputs_user2 = b.get_owned_ids(user2_vk)
expected_owned_inputs_user1 = [TransactionLink(tx.id, 0) for tx
in transactions]
expected_owned_inputs_user1 = [TransactionLink(tx_create.id, 0),
TransactionLink(tx_create.id, 1)]
assert owned_inputs_user1 == expected_owned_inputs_user1
assert owned_inputs_user2 == []
inputs = sum([tx.to_inputs() for tx in transactions], [])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
block = b.create_block([tx])
# transfer divisible asset divided in two outputs
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([user2_vk], 1), ([user2_vk], 1)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
block = b.create_block([tx_transfer_signed])
b.write_block(block, durability='hard')
owned_inputs_user1 = b.get_owned_ids(user_vk)
owned_inputs_user2 = b.get_owned_ids(user2_vk)
assert owned_inputs_user1 == []
assert owned_inputs_user2 == [TransactionLink(tx.id, 0),
TransactionLink(tx.id, 1)]
assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0),
TransactionLink(tx_transfer.id, 1)]
def test_get_owned_ids_multiple_owners(self, b, user_sk, user_vk):
from bigchaindb.common import crypto
@ -1122,7 +1032,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1134,7 +1044,7 @@ class TestMultipleInputs(object):
assert owned_inputs_user1 == owned_inputs_user2
assert owned_inputs_user1 == expected_owned_inputs_user1
tx = Transaction.transfer(tx.to_inputs(), [user3_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user3_vk], 1)], tx.asset)
tx = tx.sign([user_sk, user2_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1150,7 +1060,7 @@ class TestMultipleInputs(object):
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1164,7 +1074,7 @@ class TestMultipleInputs(object):
assert spent_inputs_user1 is None
# create a transaction and block
tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1181,7 +1091,7 @@ class TestMultipleInputs(object):
# create a new users
user2_sk, user2_vk = crypto.generate_key_pair()
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1199,7 +1109,7 @@ class TestMultipleInputs(object):
assert spent_inputs_user1 is None
# create a transaction and block
tx = Transaction.transfer(tx.to_inputs(), [user2_vk], tx.asset)
tx = Transaction.transfer(tx.to_inputs(), [([user2_vk], 1)], tx.asset)
tx = tx.sign([user_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')
@ -1214,24 +1124,23 @@ class TestMultipleInputs(object):
# Now there should be no spents (the block is invalid)
assert spent_inputs_user1 is None
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
'same asset. Remove this after implementing ',
'multiple assets'))
def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_vk):
import random
from bigchaindb.common import crypto
from bigchaindb.models import Transaction
from bigchaindb.common.transaction import Asset
# create a new users
user2_sk, user2_vk = crypto.generate_key_pair()
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_vk], payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
# create a divisible asset with 3 outputs
asset = Asset(divisible=True)
tx_create = Transaction.create([b.me],
[([user_vk], 1),
([user_vk], 1),
([user_vk], 1)],
asset=asset)
tx_create_signed = tx_create.sign([b.me_private])
block = b.create_block([tx_create_signed])
b.write_block(block, durability='hard')
owned_inputs_user1 = b.get_owned_ids(user_vk)
@ -1240,22 +1149,22 @@ class TestMultipleInputs(object):
for input_tx in owned_inputs_user1:
assert b.get_spent(input_tx.txid, input_tx.cid) is None
# select inputs to use
inputs = sum([tx.to_inputs() for tx in transactions[:2]], [])
# create a transaction and block
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
block = b.create_block([tx])
# transfer the first 2 inputs
tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2],
[([user2_vk], 1), ([user2_vk], 1)],
asset=tx_create.asset)
tx_transfer_signed = tx_transfer.sign([user_sk])
block = b.create_block([tx_transfer_signed])
b.write_block(block, durability='hard')
# check that used inputs are marked as spent
for ffill in inputs:
assert b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) == tx
for ffill in tx_create.to_inputs()[:2]:
spent_tx = b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid)
assert spent_tx == tx_transfer_signed
# check if remaining transaction that was unspent is also perceived
# spendable by BigchainDB
assert b.get_spent(transactions[2].id, 0) is None
assert b.get_spent(tx_create.to_inputs()[2].tx_input.txid, 2) is None
def test_get_spent_multiple_owners(self, b, user_sk, user_vk):
import random
@ -1268,7 +1177,8 @@ class TestMultipleInputs(object):
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([b.me], [user_vk, user2_vk], payload)
tx = Transaction.create([b.me], [([user_vk, user2_vk], 1)],
payload)
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
@ -1281,7 +1191,8 @@ class TestMultipleInputs(object):
assert b.get_spent(input_tx.txid, input_tx.cid) is None
# create a transaction
tx = Transaction.transfer(transactions[0].to_inputs(), [user3_vk], transactions[0].asset)
tx = Transaction.transfer(transactions[0].to_inputs(),
[([user3_vk], 1)], transactions[0].asset)
tx = tx.sign([user_sk, user2_sk])
block = b.create_block([tx])
b.write_block(block, durability='hard')

View File

@ -45,7 +45,7 @@ def test_create_block(b, user_vk):
block_maker = BlockPipeline()
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block_maker.create(tx)
@ -63,7 +63,7 @@ def test_write_block(b, user_vk):
txs = []
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
txs.append(tx)
@ -82,7 +82,7 @@ def test_duplicate_transaction(b, user_vk):
txs = []
for i in range(10):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
txs.append(tx)
@ -97,8 +97,7 @@ def test_duplicate_transaction(b, user_vk):
# verify tx is in the backlog
assert b.connection.run(r.table('backlog').get(txs[0].id)) is not None
# try to validate a transaction that's already in the chain; should not
# work
# try to validate a transaction that's already in the chain; should not work
assert block_maker.validate_tx(txs[0].to_dict()) is None
# duplicate tx should be removed from backlog
@ -110,7 +109,7 @@ def test_delete_tx(b, user_vk):
from bigchaindb.pipelines.block import BlockPipeline
block_maker = BlockPipeline()
for i in range(100):
tx = Transaction.create([b.me], [user_vk])
tx = Transaction.create([b.me], [([user_vk], 1)])
tx = tx.sign([b.me_private])
block_maker.create(tx)
# make sure the tx appears in the backlog
@ -139,7 +138,8 @@ def test_prefeed(b, user_vk):
from bigchaindb.pipelines.block import initial
for i in range(100):
tx = Transaction.create([b.me], [user_vk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_vk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private])
b.write_transaction(tx)
@ -168,7 +168,8 @@ def test_full_pipeline(b, user_vk):
count_assigned_to_me = 0
for i in range(100):
tx = Transaction.create([b.me], [user_vk], {'msg': random.random()})
tx = Transaction.create([b.me], [([user_vk], 1)],
{'msg': random.random()})
tx = tx.sign([b.me_private]).to_dict()
assignee = random.choice([b.me, 'aaa', 'bbb', 'ccc'])
tx['assignee'] = assignee

View File

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

View File

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

View File

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

View File

@ -62,14 +62,13 @@ def test_bigchain_class_initialization_with_parameters(config):
def test_get_blocks_status_containing_tx(monkeypatch):
from bigchaindb.db.backends.rethinkdb import RethinkDBBackend
from bigchaindb.core import Bigchain
blocks = [
{'id': 1}, {'id': 2}
]
monkeypatch.setattr(
Bigchain, 'search_block_election_on_index', lambda x, y: blocks)
monkeypatch.setattr(
Bigchain, 'block_election_status', lambda x, y, z: Bigchain.BLOCK_VALID)
monkeypatch.setattr(RethinkDBBackend, 'get_blocks_status_from_transaction', lambda x: blocks)
monkeypatch.setattr(Bigchain, 'block_election_status', lambda x, y, z: Bigchain.BLOCK_VALID)
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
with pytest.raises(Exception):
bigchain.get_blocks_status_containing_tx('txid')
@ -85,10 +84,9 @@ def test_has_previous_vote(monkeypatch):
bigchain.has_previous_vote(block)
@pytest.mark.parametrize('items,exists', (((0,), True), ((), False)))
def test_transaction_exists(monkeypatch, items, exists):
@pytest.mark.parametrize('count,exists', ((1, True), (0, False)))
def test_transaction_exists(monkeypatch, count, exists):
from bigchaindb.core import Bigchain
monkeypatch.setattr(
RqlQuery, 'run', lambda x, y: namedtuple('response', 'items')(items))
monkeypatch.setattr(RqlQuery, 'run', lambda x, y: count)
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
assert bigchain.transaction_exists('txid') is exists

View File

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

View File

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