mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merged master branch and fixed some merge conflicts
This commit is contained in:
commit
54f0d85cda
@ -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
|
||||
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -11,8 +11,8 @@ config = {
|
||||
# Note: this section supports all the Gunicorn settings:
|
||||
# - http://docs.gunicorn.org/en/stable/settings.html
|
||||
'bind': os.environ.get('BIGCHAINDB_SERVER_BIND') or 'localhost:9984',
|
||||
'workers': None, # if none, the value will be cpu_count * 2 + 1
|
||||
'threads': None, # if none, the value will be cpu_count * 2 + 1
|
||||
'workers': None, # if none, the value will be cpu_count * 2 + 1
|
||||
'threads': None, # if none, the value will be cpu_count * 2 + 1
|
||||
},
|
||||
'database': {
|
||||
'host': os.environ.get('BIGCHAINDB_DATABASE_HOST', 'localhost'),
|
||||
|
@ -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()
|
||||
@ -162,7 +161,7 @@ def run_start(args):
|
||||
|
||||
if args.allow_temp_keypair:
|
||||
if not (bigchaindb.config['keypair']['private'] or
|
||||
bigchaindb.config['keypair']['public']):
|
||||
bigchaindb.config['keypair']['public']):
|
||||
|
||||
private_key, public_key = crypto.generate_key_pair()
|
||||
bigchaindb.config['keypair']['private'] = private_key
|
||||
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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,17 +38,15 @@ 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.owners_before = owners_before
|
||||
|
||||
self.fulfillment = fulfillment
|
||||
self.tx_input = tx_input
|
||||
self.owners_before = owners_before
|
||||
|
||||
def __eq__(self, other):
|
||||
# TODO: If `other !== Fulfillment` return `False`
|
||||
@ -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,14 +222,13 @@ 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
|
||||
self.owners_after = owners_after
|
||||
|
||||
def __eq__(self, other):
|
||||
# TODO: If `other !== Condition` return `False`
|
||||
@ -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)
|
||||
|
||||
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,14 +336,11 @@ class Condition(object):
|
||||
Returns:
|
||||
:class:`cryptoconditions.ThresholdSha256Fulfillment`:
|
||||
"""
|
||||
if isinstance(current, tuple):
|
||||
owners_after, threshold = current
|
||||
else:
|
||||
owners_after = current
|
||||
try:
|
||||
threshold = len(owners_after)
|
||||
except TypeError:
|
||||
threshold = None
|
||||
owners_after = current
|
||||
try:
|
||||
threshold = len(owners_after)
|
||||
except TypeError:
|
||||
threshold = None
|
||||
|
||||
if isinstance(owners_after, list) and len(owners_after) > 1:
|
||||
ffill = ThresholdSha256Fulfillment(threshold=threshold)
|
||||
@ -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,16 +587,12 @@ 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:
|
||||
self.data = data
|
||||
|
||||
# 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):
|
||||
# TODO: If `other !== Data` return `False`
|
||||
@ -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.metadata = metadata
|
||||
|
||||
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,
|
||||
TransactionLink(self.id, cid))
|
||||
inputs.append(ffill)
|
||||
return inputs
|
||||
# NOTE: If no condition indices are passed, we just assume to
|
||||
# take all conditions as inputs.
|
||||
return [
|
||||
Fulfillment(self.conditions[cid].fulfillment,
|
||||
self.conditions[cid].owners_after,
|
||||
TransactionLink(self.id, cid))
|
||||
for cid in condition_indices or range(len(self.conditions))
|
||||
]
|
||||
|
||||
def add_fulfillment(self, fulfillment):
|
||||
"""Adds a Fulfillment to a Transaction's list of Fulfillments.
|
||||
@ -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,14 +1115,13 @@ class Transaction(object):
|
||||
tx_serialized,
|
||||
input_condition_uri)
|
||||
|
||||
if not fulfillments_count == conditions_count == \
|
||||
input_condition_uris_count:
|
||||
raise ValueError('Fulfillments, conditions and '
|
||||
if not fulfillments_count == input_condition_uris_count:
|
||||
raise ValueError('Fulfillments and '
|
||||
'input_condition_uris must have the same count')
|
||||
else:
|
||||
partial_transactions = map(gen_tx, self.fulfillments,
|
||||
self.conditions, input_condition_uris)
|
||||
return all(partial_transactions)
|
||||
|
||||
partial_transactions = map(gen_tx, self.fulfillments,
|
||||
self.conditions, input_condition_uris)
|
||||
return all(partial_transactions)
|
||||
|
||||
@staticmethod
|
||||
def _fulfillment_valid(fulfillment, operation, tx_serialized,
|
||||
@ -1240,7 +1279,10 @@ class Transaction(object):
|
||||
conditions = [Condition.from_dict(condition) for condition
|
||||
in tx['conditions']]
|
||||
metadata = Metadata.from_dict(tx['metadata'])
|
||||
asset = Asset.from_dict(tx['asset'])
|
||||
if tx['operation'] in [cls.CREATE, cls.GENESIS]:
|
||||
asset = Asset.from_dict(tx['asset'])
|
||||
else:
|
||||
asset = AssetLink.from_dict(tx['asset'])
|
||||
|
||||
return cls(tx['operation'], asset, fulfillments, conditions,
|
||||
metadata, tx['timestamp'], tx_body['version'])
|
||||
|
@ -6,7 +6,7 @@ determined according to the following rules:
|
||||
* If it's set by an environment variable, then use that value
|
||||
* Otherwise, if it's set in a local config file, then use that
|
||||
value
|
||||
* Otherwise, use the default value (contained in
|
||||
* Otherwise, use the default value (contained in
|
||||
``bigchaindb.__init__``)
|
||||
"""
|
||||
|
||||
|
@ -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,36 +238,33 @@ 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
|
||||
validity = {_id: status for _id, status in validity.items()
|
||||
if status != Bigchain.BLOCK_INVALID}
|
||||
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 = {
|
||||
@ -305,7 +315,7 @@ class Bigchain(object):
|
||||
}
|
||||
|
||||
# NOTE: If there are multiple valid blocks with this transaction,
|
||||
# something has gone wrong
|
||||
# something has gone wrong
|
||||
if list(validity.values()).count(Bigchain.BLOCK_VALID) > 1:
|
||||
block_ids = str([block for block in validity
|
||||
if validity[block] == Bigchain.BLOCK_VALID])
|
||||
@ -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,8 +409,9 @@ 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(
|
||||
txid))
|
||||
raise exceptions.DoubleSpend(
|
||||
'`{}` was spent more then once. There is a problem with the chain'.format(
|
||||
txid))
|
||||
|
||||
if num_valid_transactions:
|
||||
return Transaction.from_dict(transactions[0])
|
||||
@ -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:
|
||||
@ -439,7 +444,7 @@ class Bigchain(object):
|
||||
continue
|
||||
|
||||
# NOTE: It's OK to not serialize the transaction here, as we do not
|
||||
# use it after the execution of this function.
|
||||
# use it after the execution of this function.
|
||||
# a transaction can contain multiple outputs (conditions) so we need to iterate over all of them
|
||||
# to get a list of outputs available to spend
|
||||
for index, cond in enumerate(tx['transaction']['conditions']):
|
||||
@ -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')
|
||||
@ -588,7 +584,7 @@ class Bigchain(object):
|
||||
|
||||
def vote(self, block_id, previous_block_id, decision, invalid_reason=None):
|
||||
"""Create a signed vote for a block given the
|
||||
:attr:`previous_block_id` and the :attr:`decision` (valid/invalid).
|
||||
:attr:`previous_block_id` and the :attr:`decision` (valid/invalid).
|
||||
|
||||
Args:
|
||||
block_id (str): The id of the block to vote on.
|
||||
@ -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}'
|
||||
.format(block_id=block_id, n_votes=str(voter_counts[node]), node_id=node))
|
||||
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]
|
||||
|
@ -1,2 +1,2 @@
|
||||
# TODO can we use explicit imports?
|
||||
from bigchaindb.db.utils import *
|
||||
from bigchaindb.db.utils import * # noqa: F401,F403
|
||||
|
0
bigchaindb/db/backends/__init__.py
Normal file
0
bigchaindb/db/backends/__init__.py
Normal file
431
bigchaindb/db/backends/rethinkdb.py
Normal file
431
bigchaindb/db/backends/rethinkdb.py
Normal 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
|
@ -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')
|
||||
|
@ -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)
|
||||
@ -222,7 +276,7 @@ class Block(object):
|
||||
# https://github.com/bigchaindb/cryptoconditions/issues/27
|
||||
try:
|
||||
signature_valid = verifying_key\
|
||||
.verify(block_serialized.encode(), signature)
|
||||
.verify(block_serialized.encode(), signature)
|
||||
except ValueError:
|
||||
signature_valid = False
|
||||
if signature_valid is False:
|
||||
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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'])
|
||||
|
||||
|
@ -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):
|
||||
|
@ -12,4 +12,3 @@ def make_error(status_code, message=None):
|
||||
})
|
||||
response.status_code = status_code
|
||||
return response
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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 they’re 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).
|
||||
|
@ -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
|
||||
|
@ -41,7 +41,7 @@ extensions = [
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.napoleon',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinxcontrib.httpdomain',
|
||||
]
|
||||
|
||||
|
@ -3,21 +3,35 @@ The HTTP Client-Server API
|
||||
|
||||
.. note::
|
||||
|
||||
The HTTP client-server API is currently quite rudimentary. For example, there is no ability to do complex queries using the HTTP API. We plan to add querying capabilities in the future.
|
||||
The HTTP client-server API is currently quite rudimentary. For example,
|
||||
there is no ability to do complex queries using the HTTP API. We plan to add
|
||||
querying capabilities in the future.
|
||||
|
||||
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at the address stored in the BigchainDB node configuration settings. The default is:
|
||||
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at
|
||||
the address stored in the BigchainDB node configuration settings. The default
|
||||
is:
|
||||
|
||||
`http://localhost:9984/api/v1/ <http://localhost:9984/api/v1/>`_
|
||||
|
||||
but that address can be changed by changing the "API endpoint" configuration setting (e.g. in a local config file). There's more information about setting the API endpoint in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
|
||||
but that address can be changed by changing the "API endpoint" configuration
|
||||
setting (e.g. in a local config file). There's more information about setting
|
||||
the API endpoint in :doc:`the section about BigchainDB Configuration Settings
|
||||
<../server-reference/configuration>`.
|
||||
|
||||
There are other configuration settings related to the web server (serving the HTTP API). In particular, the default is for the web server socket to bind to ``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more details, see the "server" settings ("bind", "workers" and "threads") in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
|
||||
There are other configuration settings related to the web server (serving the
|
||||
HTTP API). In particular, the default is for the web server socket to bind to
|
||||
``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more
|
||||
details, see the "server" settings ("bind", "workers" and "threads") in
|
||||
:doc:`the section about BigchainDB Configuration Settings
|
||||
<../server-reference/configuration>`.
|
||||
|
||||
|
||||
API Root
|
||||
--------
|
||||
|
||||
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no ``/api/v1/`` on the end), then you should get an HTTP response with something like the following in the body:
|
||||
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no
|
||||
``/api/v1/`` on the end), then you should get an HTTP response with something
|
||||
like the following in the body:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@ -39,8 +53,13 @@ POST /transactions/
|
||||
.. http:post:: /transactions/
|
||||
|
||||
Push a new transaction.
|
||||
|
||||
Note: The posted transaction should be valid `transaction <https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_. The steps to build a valid transaction are beyond the scope of this page. One would normally use a driver such as the `BigchainDB Python Driver <https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to build a valid transaction.
|
||||
|
||||
Note: The posted transaction should be valid `transaction
|
||||
<https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_.
|
||||
The steps to build a valid transaction are beyond the scope of this page.
|
||||
One would normally use a driver such as the `BigchainDB Python Driver
|
||||
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to
|
||||
build a valid transaction.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@ -158,9 +177,11 @@ GET /transactions/{tx_id}/status
|
||||
|
||||
.. http:get:: /transactions/{tx_id}/status
|
||||
|
||||
Get the status of the transaction with the ID ``tx_id``, if a transaction with that ``tx_id`` exists.
|
||||
Get the status of the transaction with the ID ``tx_id``, if a transaction
|
||||
with that ``tx_id`` exists.
|
||||
|
||||
The possible status values are ``backlog``, ``undecided``, ``valid`` or ``invalid``.
|
||||
The possible status values are ``backlog``, ``undecided``, ``valid`` or
|
||||
``invalid``.
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
@ -194,7 +215,8 @@ GET /transactions/{tx_id}
|
||||
|
||||
Get the transaction with the ID ``tx_id``.
|
||||
|
||||
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED`` block on ``bigchain``, if exists.
|
||||
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED``
|
||||
block on ``bigchain``, if exists.
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
@ -260,4 +282,4 @@ GET /transactions/{tx_id}
|
||||
}
|
||||
|
||||
:statuscode 200: A transaction with that ID was found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
|
37
setup.py
37
setup.py
@ -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={
|
||||
|
@ -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
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
tx_signed.validate(b)
|
||||
|
||||
# `refillable` needs to be a boolean
|
||||
tx = Transaction.create([b.me], [user_vk])
|
||||
tx = Transaction.create([b.me], [([user_vk], 1)])
|
||||
tx.asset.refillable = 1
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
# `updatable` needs to be a boolean
|
||||
tx = Transaction.create([b.me], [user_vk])
|
||||
tx = Transaction.create([b.me], [([user_vk], 1)])
|
||||
tx.asset.updatable = 1
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
# `data` needs to be a dictionary
|
||||
tx = Transaction.create([b.me], [user_vk])
|
||||
tx = Transaction.create([b.me], [([user_vk], 1)])
|
||||
tx.asset.data = 'a'
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
# TODO: Check where to test for the amount
|
||||
"""
|
||||
tx = b.create_transaction(b.me, user_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)
|
||||
|
779
tests/assets/test_divisible_assets.py
Normal file
779
tests/assets/test_divisible_assets.py
Normal 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)
|
@ -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')
|
||||
|
@ -1,4 +1,5 @@
|
||||
from pytest import raises, mark
|
||||
from pytest import raises
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def test_fulfillment_serialization(ffill_uri, user_pub):
|
||||
@ -166,29 +167,7 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub,
|
||||
expected_threshold.add_subfulfillment(expected_simple3)
|
||||
expected.add_subfulfillment(expected_threshold)
|
||||
|
||||
cond = Condition.generate([user_pub, [user2_pub, expected_simple3]])
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
def test_generate_conditions_split_half_recursive_custom_threshold(user_pub,
|
||||
user2_pub,
|
||||
user3_pub):
|
||||
from bigchaindb.common.transaction import Condition
|
||||
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
|
||||
|
||||
expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
|
||||
expected_simple2 = Ed25519Fulfillment(public_key=user2_pub)
|
||||
expected_simple3 = Ed25519Fulfillment(public_key=user3_pub)
|
||||
|
||||
expected = ThresholdSha256Fulfillment(threshold=1)
|
||||
expected.add_subfulfillment(expected_simple1)
|
||||
expected_threshold = ThresholdSha256Fulfillment(threshold=1)
|
||||
expected_threshold.add_subfulfillment(expected_simple2)
|
||||
expected_threshold.add_subfulfillment(expected_simple3)
|
||||
expected.add_subfulfillment(expected_threshold)
|
||||
|
||||
cond = Condition.generate(([user_pub, ([user2_pub, expected_simple3], 1)],
|
||||
1))
|
||||
cond = Condition.generate([user_pub, [user2_pub, expected_simple3]], 1)
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@ -208,7 +187,7 @@ def test_generate_conditions_split_half_single_owner(user_pub, user2_pub,
|
||||
expected.add_subfulfillment(expected_threshold)
|
||||
expected.add_subfulfillment(expected_simple1)
|
||||
|
||||
cond = Condition.generate([[expected_simple2, user3_pub], user_pub])
|
||||
cond = Condition.generate([[expected_simple2, user3_pub], user_pub], 1)
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@ -225,7 +204,7 @@ def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub):
|
||||
expected.add_subfulfillment(expected_simple2)
|
||||
expected.add_subfulfillment(expected_simple3)
|
||||
|
||||
cond = Condition.generate([user_pub, user2_pub, expected_simple3])
|
||||
cond = Condition.generate([user_pub, user2_pub, expected_simple3], 1)
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@ -234,7 +213,7 @@ def test_generate_conditions_single_owner(user_pub):
|
||||
from cryptoconditions import Ed25519Fulfillment
|
||||
|
||||
expected = Ed25519Fulfillment(public_key=user_pub)
|
||||
cond = Condition.generate([user_pub])
|
||||
cond = Condition.generate([user_pub], 1)
|
||||
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
@ -244,48 +223,23 @@ def test_generate_conditions_single_owner_with_condition(user_pub):
|
||||
from cryptoconditions import Ed25519Fulfillment
|
||||
|
||||
expected = Ed25519Fulfillment(public_key=user_pub)
|
||||
cond = Condition.generate([expected])
|
||||
cond = Condition.generate([expected], 1)
|
||||
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
# TODO FOR CC: see skip reason
|
||||
@mark.skip(reason='threshold(hashlock).to_dict() exposes secret')
|
||||
def test_generate_threshold_condition_with_hashlock(user_pub, user2_pub,
|
||||
user3_pub):
|
||||
from bigchaindb.common.transaction import Condition
|
||||
from cryptoconditions import (PreimageSha256Fulfillment,
|
||||
Ed25519Fulfillment,
|
||||
ThresholdSha256Fulfillment)
|
||||
|
||||
secret = b'much secret, wow'
|
||||
hashlock = PreimageSha256Fulfillment(preimage=secret)
|
||||
|
||||
expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
|
||||
expected_simple3 = Ed25519Fulfillment(public_key=user3_pub)
|
||||
|
||||
expected = ThresholdSha256Fulfillment(threshold=2)
|
||||
expected_sub = ThresholdSha256Fulfillment(threshold=2)
|
||||
expected_sub.add_subfulfillment(expected_simple1)
|
||||
expected_sub.add_subfulfillment(hashlock)
|
||||
expected.add_subfulfillment(expected_simple3)
|
||||
|
||||
cond = Condition.generate([[user_pub, hashlock], expected_simple3])
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
def test_generate_conditions_invalid_parameters(user_pub, user2_pub,
|
||||
user3_pub):
|
||||
from bigchaindb.common.transaction import Condition
|
||||
|
||||
with raises(ValueError):
|
||||
Condition.generate([])
|
||||
Condition.generate([], 1)
|
||||
with raises(TypeError):
|
||||
Condition.generate('not a list')
|
||||
Condition.generate('not a list', 1)
|
||||
with raises(ValueError):
|
||||
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]])
|
||||
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
|
||||
with raises(ValueError):
|
||||
Condition.generate([[user_pub]])
|
||||
Condition.generate([[user_pub]], 1)
|
||||
|
||||
|
||||
def test_invalid_transaction_initialization():
|
||||
@ -321,7 +275,8 @@ def test_invalid_transaction_initialization():
|
||||
def test_create_default_asset_on_tx_initialization():
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
expected = Asset()
|
||||
asset = tx.asset
|
||||
|
||||
@ -510,10 +465,62 @@ 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
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
tx.add_fulfillment(user_ffill)
|
||||
|
||||
assert len(tx.fulfillments) == 1
|
||||
@ -522,7 +529,8 @@ def test_add_fulfillment_to_tx(user_ffill):
|
||||
def test_add_fulfillment_to_tx_with_invalid_parameters():
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
with raises(TypeError):
|
||||
tx.add_fulfillment('somewronginput')
|
||||
|
||||
@ -530,7 +538,8 @@ def test_add_fulfillment_to_tx_with_invalid_parameters():
|
||||
def test_add_condition_to_tx(user_cond):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
tx.add_condition(user_cond)
|
||||
|
||||
assert len(tx.conditions) == 1
|
||||
@ -539,7 +548,8 @@ def test_add_condition_to_tx(user_cond):
|
||||
def test_add_condition_to_tx_with_invalid_parameters():
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
with raises(TypeError):
|
||||
tx.add_condition('somewronginput')
|
||||
|
||||
@ -614,16 +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
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
with raises(TypeError):
|
||||
tx.add_condition(None)
|
||||
|
||||
|
||||
def test_cant_add_empty_fulfillment():
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
with raises(TypeError):
|
||||
tx.add_fulfillment(None)
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
@ -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,8 +721,9 @@ 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],
|
||||
transfer_tx.asset)
|
||||
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(),
|
||||
[([user_vk], 1)],
|
||||
transfer_tx.asset)
|
||||
tx_invalid = tx_invalid.sign([user_sk])
|
||||
|
||||
with pytest.raises(TransactionNotInValidBlock):
|
||||
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user