diff --git a/.travis.yml b/.travis.yml index b5a7b878..a315789c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ sudo: required language: python +cache: pip python: - 3.4 - 3.5 diff --git a/benchmarking-tests/benchmark_utils.py b/benchmarking-tests/benchmark_utils.py index 86242753..ce462bdf 100644 --- a/benchmarking-tests/benchmark_utils.py +++ b/benchmarking-tests/benchmark_utils.py @@ -8,7 +8,7 @@ import logging import rethinkdb as r from os.path import expanduser -from bigchaindb_common.transaction import Transaction +from bigchaindb.common.transaction import Transaction from bigchaindb import Bigchain from bigchaindb.util import ProcessGroup diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index cd5ce6db..40891416 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -13,8 +13,8 @@ import builtins import logstats -from bigchaindb_common import crypto -from bigchaindb_common.exceptions import (StartupError, +from bigchaindb.common import crypto +from bigchaindb.common.exceptions import (StartupError, DatabaseAlreadyExists, KeypairNotFoundException) import rethinkdb as r diff --git a/bigchaindb/commands/utils.py b/bigchaindb/commands/utils.py index 1391f18f..6619da6d 100644 --- a/bigchaindb/commands/utils.py +++ b/bigchaindb/commands/utils.py @@ -3,7 +3,7 @@ for ``argparse.ArgumentParser``. """ import argparse -from bigchaindb_common.exceptions import StartupError +from bigchaindb.common.exceptions import StartupError import multiprocessing as mp import subprocess @@ -19,14 +19,9 @@ def start_rethinkdb(): """Start RethinkDB as a child process and wait for it to be available. - Args: - wait_for_db (bool): wait for the database to be ready - extra_opts (list): a list of extra options to be used when - starting the db - Raises: - ``bigchaindb_common.exceptions.StartupError`` if RethinkDB cannot - be started. + :class:`~bigchaindb.common.exceptions.StartupError` if + RethinkDB cannot be started. """ proc = subprocess.Popen(['rethinkdb', '--bind', 'all'], diff --git a/bigchaindb/common/__init__.py b/bigchaindb/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bigchaindb/common/crypto.py b/bigchaindb/common/crypto.py new file mode 100644 index 00000000..e440f81d --- /dev/null +++ b/bigchaindb/common/crypto.py @@ -0,0 +1,18 @@ +# Separate all crypto code so that we can easily test several implementations + +import sha3 +from cryptoconditions import crypto + + +def hash_data(data): + """Hash the provided data using SHA3-256""" + return sha3.sha3_256(data.encode()).hexdigest() + + +def generate_key_pair(): + # TODO FOR CC: Adjust interface so that this function becomes unnecessary + private_key, public_key = crypto.ed25519_generate_key_pair() + return private_key.decode(), public_key.decode() + +SigningKey = crypto.Ed25519SigningKey +VerifyingKey = crypto.Ed25519VerifyingKey diff --git a/bigchaindb/common/exceptions.py b/bigchaindb/common/exceptions.py new file mode 100644 index 00000000..2e1dc670 --- /dev/null +++ b/bigchaindb/common/exceptions.py @@ -0,0 +1,82 @@ +"""Custom exceptions used in the `bigchaindb` package. +""" + + +class ConfigurationError(Exception): + """Raised when there is a problem with server configuration""" + + +class OperationError(Exception): + """Raised when an operation cannot go through""" + + +class TransactionDoesNotExist(Exception): + """Raised if the transaction is not in the database""" + + +class TransactionOwnerError(Exception): + """Raised if a user tries to transfer a transaction they don't own""" + + +class DoubleSpend(Exception): + """Raised if a double spend is found""" + + +class InvalidHash(Exception): + """Raised if there was an error checking the hash for a particular + operation""" + + +class InvalidSignature(Exception): + """Raised if there was an error checking the signature for a particular + operation""" + + +class DatabaseAlreadyExists(Exception): + """Raised when trying to create the database but the db is already there""" + + +class DatabaseDoesNotExist(Exception): + """Raised when trying to delete the database but the db is not there""" + + +class KeypairNotFoundException(Exception): + """Raised if operation cannot proceed because the keypair was not given""" + + +class KeypairMismatchException(Exception): + """Raised if the private key(s) provided for signing don't match any of the + current owner(s)""" + + +class StartupError(Exception): + """Raised when there is an error starting up the system""" + + +class ImproperVoteError(Exception): + """Raised if a vote is not constructed correctly, or signed incorrectly""" + + +class MultipleVotesError(Exception): + """Raised if a voter has voted more than once""" + + +class GenesisBlockAlreadyExistsError(Exception): + """Raised when trying to create the already existing genesis block""" + + +class CyclicBlockchainError(Exception): + """Raised when there is a cycle in the blockchain""" + + +class FulfillmentNotInValidBlock(Exception): + """Raised when a transaction depends on an invalid or undecided + fulfillment""" + + +class AssetIdMismatch(Exception): + """Raised when multiple transaction inputs related to different assets""" + + +class AmountError(Exception): + """Raised when the amount of a non-divisible asset is different then 1""" diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py new file mode 100644 index 00000000..41d8a30c --- /dev/null +++ b/bigchaindb/common/transaction.py @@ -0,0 +1,1246 @@ +from copy import deepcopy +from functools import reduce +from uuid import uuid4 + +from cryptoconditions import (Fulfillment as CCFulfillment, + ThresholdSha256Fulfillment, Ed25519Fulfillment, + PreimageSha256Fulfillment) +from cryptoconditions.exceptions import ParsingError + +from bigchaindb.common.crypto import SigningKey, hash_data +from bigchaindb.common.exceptions import (KeypairMismatchException, + InvalidHash, InvalidSignature) +from bigchaindb.common.util import serialize, gen_timestamp + + +class Fulfillment(object): + """A Fulfillment is used to spend assets locked by a Condition. + + Attributes: + fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment + to be signed with a private key. + owners_before (:obj:`list` of :obj:`str`): A list of owners after a + Transaction was confirmed. + tx_input (:class:`~bigchaindb.common.transaction. TransactionLink`, + optional): A link representing the input of a `TRANSFER` + Transaction. + """ + + def __init__(self, fulfillment, owners_before, tx_input=None): + """Fulfillment shims a Cryptocondition Fulfillment for BigchainDB. + + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to be signed with a private key. + owners_before (:obj:`list` of :obj:`str`): A list of owners + after a Transaction was confirmed. + tx_input (:class:`~bigchaindb.common.transaction. + 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 + + def __eq__(self, other): + # TODO: If `other !== Fulfillment` return `False` + return self.to_dict() == other.to_dict() + + def to_dict(self, fid=None): + """Transforms the object to a Python dictionary. + + Note: + A `fid` can be submitted to be included in the dictionary + representation. + + If a Fulfillment hasn't been signed yet, this method returns a + dictionary representation. + + Args: + fid (int, optional): The Fulfillment's index in a Transaction. + + Returns: + dict: The Fulfillment as an alternative serialization format. + """ + try: + fulfillment = self.fulfillment.serialize_uri() + except (TypeError, AttributeError): + # NOTE: When a non-signed transaction is casted to a dict, + # `self.fulfillments` value is lost, as in the node's + # transaction model that is saved to the database, does not + # account for its dictionary form but just for its signed uri + # form. + # Hence, when a non-signed fulfillment is to be cast to a + # dict, we just call its internal `to_dict` method here and + # its `from_dict` method in `Fulfillment.from_dict`. + fulfillment = self.fulfillment.to_dict() + + try: + # NOTE: `self.tx_input` can be `None` and that's fine + tx_input = self.tx_input.to_dict() + except AttributeError: + tx_input = None + + ffill = { + 'owners_before': self.owners_before, + 'input': tx_input, + 'fulfillment': fulfillment, + } + if fid is not None: + ffill['fid'] = fid + return ffill + + @classmethod + def from_dict(cls, ffill): + """Transforms a Python dictionary to a Fulfillment object. + + Note: + Optionally, this method can also serialize a Cryptoconditions- + Fulfillment that is not yet signed. + + Args: + ffill (dict): The Fulfillment to be transformed. + + Returns: + :class:`~bigchaindb.common.transaction.Fulfillment` + + Raises: + InvalidSignature: If a Fulfillment's URI couldn't be parsed. + """ + try: + fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) + except ValueError: + # TODO FOR CC: Throw an `InvalidSignature` error in this case. + raise InvalidSignature("Fulfillment URI couldn't been parsed") + except TypeError: + # NOTE: See comment about this special case in + # `Fulfillment.to_dict` + fulfillment = CCFulfillment.from_dict(ffill['fulfillment']) + input_ = TransactionLink.from_dict(ffill['input']) + return cls(fulfillment, ffill['owners_before'], input_) + + +class TransactionLink(object): + """An object for unidirectional linking to a Transaction's Condition. + + Attributes: + txid (str, optional): A Transaction to link to. + cid (int, optional): A Condition's index in a Transaction with id + `txid`. + """ + + def __init__(self, txid=None, cid=None): + """Used to point to a specific Condition of a Transaction. + + Note: + In an IPLD implementation, this class is not necessary anymore, + as an IPLD link can simply point to an object, as well as an + objects properties. So instead of having a (de)serializable + class, we can have a simple IPLD link of the form: + `//transaction/conditions//`. + + Args: + txid (str, optional): A Transaction to link to. + cid (int, optional): A Condition's index in a Transaction with + id `txid`. + """ + self.txid = txid + self.cid = cid + + def __bool__(self): + return self.txid is not None and self.cid is not None + + def __eq__(self, other): + # TODO: If `other !== TransactionLink` return `False` + return self.to_dict() == self.to_dict() + + @classmethod + def from_dict(cls, link): + """Transforms a Python dictionary to a TransactionLink object. + + Args: + link (dict): The link to be transformed. + + Returns: + :class:`~bigchaindb.common.transaction.TransactionLink` + """ + try: + return cls(link['txid'], link['cid']) + 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.txid is None and self.cid is None: + return None + else: + return { + 'txid': self.txid, + 'cid': self.cid, + } + + +class Condition(object): + """A Condition is used to lock an asset. + + Attributes: + fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment + to extract a Condition from. + owners_after (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + """ + + def __init__(self, fulfillment, owners_after=None, amount=1): + """Condition shims a Cryptocondition condition for BigchainDB. + + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to extract a Condition from. + owners_after (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + amount (int): The amount of Assets to be locked with this + Condition. + + Raises: + TypeError: if `owners_after` is not instance of `list`. + """ + self.fulfillment = fulfillment + # TODO: Not sure if we should validate for value here + self.amount = amount + + if not isinstance(owners_after, list) and owners_after is not None: + raise TypeError('`owners_after` must be a list instance or None') + else: + self.owners_after = owners_after + + def __eq__(self, other): + # TODO: If `other !== Condition` return `False` + return self.to_dict() == other.to_dict() + + def to_dict(self, cid=None): + """Transforms the object to a Python dictionary. + + Note: + A `cid` can be submitted to be included in the dictionary + representation. + + A dictionary serialization of the Fulfillment the Condition was + derived from is always provided. + + Args: + cid (int, optional): The Condition's index in a Transaction. + + Returns: + dict: The Condition as an alternative serialization format. + """ + # TODO FOR CC: It must be able to recognize a hashlock condition + # and fulfillment! + condition = {} + try: + condition['details'] = self.fulfillment.to_dict() + except AttributeError: + pass + + try: + condition['uri'] = self.fulfillment.condition_uri + except AttributeError: + condition['uri'] = self.fulfillment + + cond = { + 'owners_after': self.owners_after, + 'condition': condition, + 'amount': self.amount + } + if cid is not None: + cond['cid'] = cid + return cond + + @classmethod + def generate(cls, owners_after): + """Generates a Condition from a specifically formed tuple or list. + + Note: + If a ThresholdCondition has to be generated where the threshold + is always the number of subconditions it is split between, a + list of the following structure is sufficient: + + [(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. + + Returns: + A Condition that can be used in a Transaction. + + Returns: + TypeError: If `owners_after` is not an instance of `list`. + TypeError: If `owners_after` is an empty list. + """ + # TODO: We probably want to remove the tuple logic for weights here + # again: + # github.com/bigchaindb/bigchaindb/issues/730#issuecomment-255144756 + if isinstance(owners_after, tuple): + owners_after, threshold = owners_after + else: + threshold = len(owners_after) + + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be an instance of list') + if len(owners_after) == 0: + raise ValueError('`owners_after` needs to contain at least one' + 'owner') + elif len(owners_after) == 1 and not isinstance(owners_after[0], list): + try: + ffill = Ed25519Fulfillment(public_key=owners_after[0]) + except TypeError: + ffill = owners_after[0] + return cls(ffill, owners_after) + else: + initial_cond = ThresholdSha256Fulfillment(threshold=threshold) + threshold_cond = reduce(cls._gen_condition, owners_after, + initial_cond) + return cls(threshold_cond, owners_after) + + @classmethod + def _gen_condition(cls, initial, current): + """Generates ThresholdSha256 conditions from a list of new owners. + + Note: + This method is intended only to be used with a reduce function. + For a description on how to use this method, see + `Condition.generate`. + + Args: + initial (:class:`cryptoconditions.ThresholdSha256Fulfillment`): + A Condition representing the overall root. + current (:obj:`list` of :obj:`str`|str): A list of new owners + or a single new owner. + + 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 + + if isinstance(owners_after, list) and len(owners_after) > 1: + ffill = ThresholdSha256Fulfillment(threshold=threshold) + reduce(cls._gen_condition, owners_after, ffill) + elif isinstance(owners_after, list) and len(owners_after) <= 1: + raise ValueError('Sublist cannot contain single owner') + else: + try: + owners_after = owners_after.pop() + except AttributeError: + pass + try: + ffill = Ed25519Fulfillment(public_key=owners_after) + except TypeError: + # NOTE: Instead of submitting base58 encoded addresses, a user + # of this class can also submit fully instantiated + # Cryptoconditions. In the case of casting `owners_after` + # to a Ed25519Fulfillment with the result of a + # `TypeError`, we're assuming that `owners_after` is a + # Cryptocondition then. + ffill = owners_after + initial.add_subfulfillment(ffill) + return initial + + @classmethod + def from_dict(cls, cond): + """Transforms a Python dictionary to a Condition object. + + Note: + To pass a serialization cycle multiple times, a + Cryptoconditions Fulfillment needs to be present in the + passed-in dictionary, as Condition URIs are not serializable + anymore. + + Args: + cond (dict): The Condition to be transformed. + + Returns: + :class:`~bigchaindb.common.transaction.Condition` + """ + try: + fulfillment = CCFulfillment.from_dict(cond['condition']['details']) + except KeyError: + # NOTE: Hashlock condition case + fulfillment = cond['condition']['uri'] + return cls(fulfillment, cond['owners_after'], cond['amount']) + + +class Asset(object): + """An Asset is a fungible unit to spend and lock with Transactions. + + Note: + Currently, the following flags are not yet fully supported: + - `divisible` + - `updatable` + - `refillable` + + Attributes: + data (dict): A dictionary of data that can be added to an Asset. + data_id (str): A unique identifier of `data`'s content. + divisible (bool): A flag indicating if an Asset can be divided. + updatable (bool): A flag indicating if an Asset can be updated. + refillable (bool): A flag indicating if an Asset can be refilled. + """ + + def __init__(self, data=None, data_id=None, divisible=False, + updatable=False, refillable=False): + """An Asset is not required to contain any extra data from outside.""" + self.data = data + self.data_id = data_id if data_id is not None else self.to_hash() + self.divisible = divisible + self.updatable = updatable + self.refillable = refillable + + self._validate_asset() + + def __eq__(self, other): + try: + other_dict = other.to_dict() + except AttributeError: + return False + return self.to_dict() == other_dict + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + (dict): The Asset object as an alternative serialization + format. + """ + return { + 'id': self.data_id, + 'divisible': self.divisible, + 'updatable': self.updatable, + 'refillable': self.refillable, + 'data': self.data, + } + + @classmethod + def from_dict(cls, asset): + """Transforms a Python dictionary to an Asset object. + + Args: + asset (dict): The dictionary to be serialized. + + Returns: + :class:`~bigchaindb.common.transaction.Asset` + """ + return cls(asset.get('data'), asset['id'], + asset.get('divisible', False), + asset.get('updatable', False), + asset.get('refillable', False)) + + def to_hash(self): + """Generates a unqiue uuid for an Asset""" + return str(uuid4()) + + def _validate_asset(self): + """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') + if not isinstance(self.divisible, bool): + raise TypeError('`divisible` must be a boolean') + if not isinstance(self.refillable, bool): + raise TypeError('`refillable` must be a boolean') + if not isinstance(self.updatable, bool): + raise TypeError('`updatable` must be a boolean') + + +class Metadata(object): + """Metadata is used to store a dictionary and its hash in a Transaction.""" + + def __init__(self, data=None, data_id=None): + """Metadata stores a payload `data` as well as data's hash, `data_id`. + + Note: + When no `data_id` is provided, one is being generated by + this method. + + Args: + data (dict): A dictionary to be held by Metadata. + 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 + + def __eq__(self, other): + # TODO: If `other !== Data` return `False` + return self.to_dict() == other.to_dict() + + @classmethod + def from_dict(cls, data): + """Transforms a Python dictionary to a Metadata object. + + Args: + data (dict): The dictionary to be serialized. + + Returns: + :class:`~bigchaindb.common.transaction.Metadata` + """ + try: + return cls(data['data'], data['id']) + except TypeError: + return cls() + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + (dict|None): The Metadata object as an alternative + serialization format. + """ + if self.data is None: + return None + else: + return { + 'data': self.data, + 'id': self.data_id, + } + + def to_hash(self): + """A hash corresponding to the contents of `payload`.""" + return str(uuid4()) + + +class Transaction(object): + """A Transaction is used to create and transfer assets. + + Note: + For adding Fulfillments and Conditions, this class provides methods + to do so. + + Attributes: + operation (str): Defines the operation of the Transaction. + fulfillments (:obj:`list` of :class:`~bigchaindb.common. + transaction.Fulfillment`, optional): Define the assets to + spend. + conditions (:obj:`list` of :class:`~bigchaindb.common. + transaction.Condition`, optional): Define the assets to lock. + metadata (:class:`~bigchaindb.common.transaction.Metadata`): + Metadata to be stored along with the Transaction. + timestamp (int): Defines the time a Transaction was created. + version (int): Defines the version number of a Transaction. + """ + CREATE = 'CREATE' + TRANSFER = 'TRANSFER' + GENESIS = 'GENESIS' + ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS) + VERSION = 1 + + def __init__(self, operation, asset, fulfillments=None, conditions=None, + metadata=None, timestamp=None, version=None): + """The constructor allows to create a customizable Transaction. + + Note: + When no `version` or `timestamp`, is provided, one is being + generated by this method. + + Args: + operation (str): Defines the operation of the Transaction. + asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset + to be transferred or created in a Transaction. + fulfillments (:obj:`list` of :class:`~bigchaindb.common. + transaction.Fulfillment`, optional): Define the assets to + spend. + conditions (:obj:`list` of :class:`~bigchaindb.common. + transaction.Condition`, optional): Define the assets to + lock. + metadata (:class:`~bigchaindb.common.transaction.Metadata`): + Metadata to be stored along with the Transaction. + timestamp (int): Defines the time a Transaction was created. + 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): + raise TypeError('`asset` must be an Asset instance') + else: + self.asset = asset + + if conditions is not None 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): + 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 + + @classmethod + def create(cls, owners_before, owners_after, metadata=None, asset=None, + secret=None, time_expire=None): + """A simple way to generate a `CREATE` transaction. + + Note: + This method currently supports the following Cryptoconditions + use cases: + - Ed25519 + - ThresholdSha256 + - PreimageSha256. + + Additionally, it provides support for the following BigchainDB + use cases: + - Multiple inputs and outputs. + + Args: + owners_before (:obj:`list` of :obj:`str`): A list of keys that + represent the creators of this asset. + owners_after (:obj:`list` of :obj:`str`): A list of keys that + represent the receivers of this Transaction. + metadata (dict): Python dictionary to be stored along with the + 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` + """ + if not isinstance(owners_before, list): + raise TypeError('`owners_before` must be a list instance') + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be a list instance') + + 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) + + 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) + + 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) + + 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 ;)") + + @classmethod + def transfer(cls, inputs, owners_after, asset, metadata=None): + """A simple way to generate a `TRANSFER` transaction. + + Note: + Different cases for threshold conditions: + + Combining multiple `inputs` with an arbitrary number of + `owners_after` can yield interesting cases for the creation of + threshold conditions we'd like to support. The following + notation is proposed: + + 1. The index of an `owner_after` corresponds to the index of + an input: + e.g. `transfer([input1], [a])`, means `input1` would now be + owned by user `a`. + + 2. `owners_after` can (almost) get arbitrary deeply nested, + creating various complex threshold conditions: + e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means + `a`'s signature would have a 50% weight on `inp1` + compared to `b` and `c` that share 25% of the leftover + weight respectively. `inp2` is owned completely by `d`. + + Args: + inputs (:obj:`list` of :class:`~bigchaindb.common.transaction. + Fulfillment`): Converted "output" Conditions, intended to + be used as "input" Fulfillments in the transfer to + generate. + owners_after (:obj:`list` of :obj:`str`): A list of keys that + represent the receivers of this Transaction. + asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset + to be transferred in this Transaction. + metadata (dict): Python dictionary to be stored along with the + Transaction. + + Returns: + :class:`~bigchaindb.common.transaction.Transaction` + """ + if not isinstance(inputs, list): + raise TypeError('`inputs` must be a list instance') + if len(inputs) == 0: + raise ValueError('`inputs` must contain at least one item') + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be a list instance') + + # 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") + + metadata = Metadata(metadata) + inputs = deepcopy(inputs) + return cls(cls.TRANSFER, asset, inputs, conditions, metadata) + + def __eq__(self, other): + try: + other = other.to_dict() + except AttributeError: + return False + return self.to_dict() == other + + def to_inputs(self, condition_indices=None): + """Converts a Transaction's Conditions to spendable Fulfillments. + + Note: + Takes the Transaction's Conditions and derives Fulfillments + from it that can then be passed into `Transaction.transfer` as + `inputs`. + A list of integers can be passed to `condition_indices` that + defines which Conditions should be returned as inputs. + If no `condition_indices` are passed (empty list or None) all + Condition of the Transaction are passed. + + Args: + condition_indices (:obj:`list` of int): Defines which + Conditions should be returned as inputs. + + Returns: + :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 + + def add_fulfillment(self, fulfillment): + """Adds a Fulfillment to a Transaction's list of Fulfillments. + + Args: + fulfillment (:class:`~bigchaindb.common.transaction. + Fulfillment`): A Fulfillment to be added to the + Transaction. + """ + if not isinstance(fulfillment, Fulfillment): + raise TypeError('`fulfillment` must be a Fulfillment instance') + self.fulfillments.append(fulfillment) + + def add_condition(self, condition): + """Adds a Condition to a Transaction's list of Conditions. + + Args: + condition (:class:`~bigchaindb.common.transaction. + Condition`): A Condition to be added to the + Transaction. + """ + if not isinstance(condition, Condition): + raise TypeError('`condition` must be a Condition instance or None') + self.conditions.append(condition) + + def sign(self, private_keys): + """Fulfills a previous Transaction's Condition by signing Fulfillments. + + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256Fulfillment + Furthermore, note that all keys required to fully sign the + Transaction have to be passed to this method. A subset of all + will cause this method to fail. + + Args: + private_keys (:obj:`list` of :obj:`str`): A complete list of + all private keys needed to sign all Fulfillments of this + Transaction. + + Returns: + :class:`~bigchaindb.common.transaction.Transaction` + """ + # TODO: Singing should be possible with at least one of all private + # keys supplied to this method. + if private_keys is None or not isinstance(private_keys, list): + raise TypeError('`private_keys` must be a list instance') + + # NOTE: Generate public keys from private keys and match them in a + # dictionary: + # key: public_key + # value: private_key + def gen_public_key(private_key): + # TODO FOR CC: Adjust interface so that this function becomes + # unnecessary + + # cc now provides a single method `encode` to return the key + # in several different encodings. + public_key = private_key.get_verifying_key().encode() + # Returned values from cc are always bytestrings so here we need + # to decode to convert the bytestring into a python str + return public_key.decode() + + 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: + # 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.timestamp, self.version) + + tx_partial_dict = tx_partial.to_dict() + tx_partial_dict = Transaction._remove_signatures(tx_partial_dict) + tx_serialized = Transaction._to_str(tx_partial_dict) + self._sign_fulfillment(fulfillment, index, tx_serialized, + key_pairs) + return self + + def _sign_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + """Signs a single Fulfillment with a partial Transaction as message. + + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256Fulfillment. + + Args: + fulfillment (:class:`~bigchaindb.common.transaction. + Fulfillment`) The Fulfillment to be signed. + index (int): The index (or `fid`) of the Fulfillment to be + signed. + tx_serialized (str): The Transaction to be used as message. + key_pairs (dict): The keys to sign the Transaction with. + """ + if isinstance(fulfillment.fulfillment, Ed25519Fulfillment): + self._sign_simple_signature_fulfillment(fulfillment, index, + tx_serialized, key_pairs) + elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): + self._sign_threshold_signature_fulfillment(fulfillment, index, + tx_serialized, + key_pairs) + else: + raise ValueError("Fulfillment couldn't be matched to " + 'Cryptocondition fulfillment type.') + + def _sign_simple_signature_fulfillment(self, fulfillment, index, + tx_serialized, key_pairs): + """Signs a Ed25519Fulfillment. + + Args: + fulfillment (:class:`~bigchaindb.common.transaction. + Fulfillment`) The Fulfillment to be signed. + index (int): The index (or `fid`) of the Fulfillment to be + signed. + tx_serialized (str): The Transaction to be used as message. + key_pairs (dict): The keys to sign the Transaction with. + """ + # NOTE: To eliminate the dangers of accidentally signing a condition by + # reference, we remove the reference of fulfillment here + # intentionally. If the user of this class knows how to use it, + # this should never happen, but then again, never say never. + fulfillment = deepcopy(fulfillment) + owner_before = fulfillment.owners_before[0] + try: + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + fulfillment.fulfillment.sign(tx_serialized.encode(), + key_pairs[owner_before]) + except KeyError: + raise KeypairMismatchException('Public key {} is not a pair to ' + 'any of the private keys' + .format(owner_before)) + self.fulfillments[index] = fulfillment + + def _sign_threshold_signature_fulfillment(self, fulfillment, index, + tx_serialized, key_pairs): + """Signs a ThresholdSha256Fulfillment. + + Args: + fulfillment (:class:`~bigchaindb.common.transaction. + Fulfillment`) The Fulfillment to be signed. + index (int): The index (or `fid`) of the Fulfillment to be + signed. + tx_serialized (str): The Transaction to be used as message. + key_pairs (dict): The keys to sign the Transaction with. + """ + fulfillment = deepcopy(fulfillment) + for owner_before in fulfillment.owners_before: + try: + # TODO: CC should throw a KeypairMismatchException, instead of + # our manual mapping here + + # TODO FOR CC: Naming wise this is not so smart, + # `get_subcondition` in fact doesn't return a + # condition but a fulfillment + + # TODO FOR CC: `get_subcondition` is singular. One would not + # expect to get a list back. + ccffill = fulfillment.fulfillment + subffill = ccffill.get_subcondition_from_vk(owner_before)[0] + except IndexError: + raise KeypairMismatchException('Public key {} cannot be found ' + 'in the fulfillment' + .format(owner_before)) + try: + private_key = key_pairs[owner_before] + except KeyError: + raise KeypairMismatchException('Public key {} is not a pair ' + 'to any of the private keys' + .format(owner_before)) + + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + subffill.sign(tx_serialized.encode(), private_key) + self.fulfillments[index] = fulfillment + + def fulfillments_valid(self, input_conditions=None): + """Validates the Fulfillments in the Transaction against given + Conditions. + + Note: + Given a `CREATE` or `GENESIS` Transaction is passed, + dummyvalues for Conditions are submitted for validation that + evaluate parts of the validation-checks to `True`. + + Args: + input_conditions (:obj:`list` of :class:`~bigchaindb.common. + transaction.Condition`): A list of Conditions to check the + Fulfillments against. + + Returns: + bool: If all Fulfillments are valid. + """ + if self.operation in (Transaction.CREATE, Transaction.GENESIS): + # NOTE: Since in the case of a `CREATE`-transaction we do not have + # to check for input_conditions, we're just submitting dummy + # values to the actual method. This simplifies it's logic + # greatly, as we do not have to check against `None` values. + return self._fulfillments_valid(['dummyvalue' + for cond in self.fulfillments]) + elif self.operation == Transaction.TRANSFER: + return self._fulfillments_valid([cond.fulfillment.condition_uri + for cond in input_conditions]) + else: + allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) + raise TypeError('`operation` must be one of {}' + .format(allowed_ops)) + + def _fulfillments_valid(self, input_condition_uris): + """Validates a Fulfillment against a given set of Conditions. + + Note: + The number of `input_condition_uris` must be equal to the + number of Fulfillments a Transaction has. + + Args: + input_condition_uris (:obj:`list` of :obj:`str`): A list of + Conditions to check the Fulfillments against. + + Returns: + bool: If all Fulfillments are valid. + """ + 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.version) + tx_dict = tx.to_dict() + tx_dict = Transaction._remove_signatures(tx_dict) + tx_serialized = Transaction._to_str(tx_dict) + + # TODO: Use local reference to class, not `Transaction.` + return Transaction._fulfillment_valid(fulfillment, self.operation, + tx_serialized, + input_condition_uri) + + if not fulfillments_count == conditions_count == \ + input_condition_uris_count: + raise ValueError('Fulfillments, conditions 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) + + @staticmethod + def _fulfillment_valid(fulfillment, operation, tx_serialized, + input_condition_uri=None): + """Validates a single Fulfillment against a single Condition. + + Note: + In case of a `CREATE` or `GENESIS` Transaction, this method + does not validate against `input_condition_uri`. + + Args: + fulfillment (:class:`~bigchaindb.common.transaction. + Fulfillment`) The Fulfillment to be signed. + operation (str): The type of Transaction. + tx_serialized (str): The Transaction used as a message when + initially signing it. + input_condition_uri (str, optional): A Condition to check the + Fulfillment against. + + Returns: + bool: If the Fulfillment is valid. + """ + ccffill = fulfillment.fulfillment + try: + parsed_ffill = CCFulfillment.from_uri(ccffill.serialize_uri()) + except (TypeError, ValueError, ParsingError): + return False + + if operation in (Transaction.CREATE, Transaction.GENESIS): + # NOTE: In the case of a `CREATE` or `GENESIS` transaction, the + # input condition is always validate to `True`. + input_cond_valid = True + else: + input_cond_valid = input_condition_uri == ccffill.condition_uri + + # NOTE: We pass a timestamp to `.validate`, as in case of a timeout + # condition we'll have to validate against it + + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + return parsed_ffill.validate(message=tx_serialized.encode(), + now=gen_timestamp()) and input_cond_valid + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + dict: The Transaction as an alternative serialization format. + """ + try: + metadata = self.metadata.to_dict() + except AttributeError: + # NOTE: metadata can be None and that's OK + metadata = None + + if self.operation in (self.__class__.GENESIS, self.__class__.CREATE): + asset = self.asset.to_dict() + else: + # NOTE: An `asset` in a `TRANSFER` only contains the asset's id + asset = {'id': self.asset.data_id} + + tx_body = { + 'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment + in enumerate(self.fulfillments)], + 'conditions': [condition.to_dict(cid) for cid, condition + in enumerate(self.conditions)], + 'operation': str(self.operation), + 'timestamp': self.timestamp, + 'metadata': metadata, + 'asset': asset, + } + tx = { + 'version': self.version, + 'transaction': tx_body, + } + + tx_no_signatures = Transaction._remove_signatures(tx) + tx_serialized = Transaction._to_str(tx_no_signatures) + tx_id = Transaction._to_hash(tx_serialized) + + tx['id'] = tx_id + return tx + + @staticmethod + # TODO: Remove `_dict` prefix of variable. + def _remove_signatures(tx_dict): + """Takes a Transaction dictionary and removes all signatures. + + Args: + tx_dict (dict): The Transaction to remove all signatures from. + + Returns: + dict + + """ + # NOTE: We remove the reference since we need `tx_dict` only for the + # transaction's hash + tx_dict = deepcopy(tx_dict) + for fulfillment in tx_dict['transaction']['fulfillments']: + # NOTE: Not all Cryptoconditions return a `signature` key (e.g. + # ThresholdSha256Fulfillment), so setting it to `None` in any + # case could yield incorrect signatures. This is why we only + # set it to `None` if it's set in the dict. + fulfillment['fulfillment'] = None + return tx_dict + + @staticmethod + def _to_hash(value): + return hash_data(value) + + @property + def id(self): + return self.to_hash() + + def to_hash(self): + return self.to_dict()['id'] + + @staticmethod + def _to_str(value): + return serialize(value) + + # TODO: This method shouldn't call `_remove_signatures` + def __str__(self): + tx = Transaction._remove_signatures(self.to_dict()) + return Transaction._to_str(tx) + + @classmethod + # TODO: Make this method more pretty + def from_dict(cls, tx_body): + """Transforms a Python dictionary to a Transaction object. + + Args: + tx_body (dict): The Transaction to be transformed. + + Returns: + :class:`~bigchaindb.common.transaction.Transaction` + """ + # NOTE: Remove reference to avoid side effects + tx_body = deepcopy(tx_body) + try: + proposed_tx_id = tx_body.pop('id') + except KeyError: + raise InvalidHash() + + tx_body_no_signatures = Transaction._remove_signatures(tx_body) + tx_body_serialized = Transaction._to_str(tx_body_no_signatures) + valid_tx_id = Transaction._to_hash(tx_body_serialized) + + if proposed_tx_id != valid_tx_id: + raise InvalidHash() + else: + tx = tx_body['transaction'] + fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment + in tx['fulfillments']] + conditions = [Condition.from_dict(condition) for condition + in tx['conditions']] + metadata = Metadata.from_dict(tx['metadata']) + asset = Asset.from_dict(tx['asset']) + + return cls(tx['operation'], asset, fulfillments, conditions, + metadata, tx['timestamp'], tx_body['version']) diff --git a/bigchaindb/common/util.py b/bigchaindb/common/util.py new file mode 100644 index 00000000..f6f671db --- /dev/null +++ b/bigchaindb/common/util.py @@ -0,0 +1,48 @@ +import time + +import rapidjson + + +def gen_timestamp(): + """The Unix time, rounded to the nearest second. + See https://en.wikipedia.org/wiki/Unix_time + + Returns: + str: the Unix time + """ + return str(round(time.time())) + + +def serialize(data): + """Serialize a dict into a JSON formatted string. + + This function enforces rules like the separator and order of keys. + This ensures that all dicts are serialized in the same way. + + This is specially important for hashing data. We need to make sure that + everyone serializes their data in the same way so that we do not have + hash mismatches for the same structure due to serialization + differences. + + Args: + data (dict): dict to serialize + + Returns: + str: JSON formatted string + + """ + return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, + sort_keys=True) + + +def deserialize(data): + """Deserialize a JSON formatted string into a dict. + + Args: + data (str): JSON formatted string. + + Returns: + dict: dict resulting from the serialization of a JSON formatted + string. + """ + return rapidjson.loads(data) diff --git a/bigchaindb/config_utils.py b/bigchaindb/config_utils.py index f72c348b..3b571835 100644 --- a/bigchaindb/config_utils.py +++ b/bigchaindb/config_utils.py @@ -17,7 +17,7 @@ import json import logging import collections -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions import bigchaindb diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 3930496d..83b85652 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -4,9 +4,9 @@ import collections 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 +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 @@ -198,10 +198,10 @@ class Bigchain(object): the return value is then a tuple: (tx, status) Returns: - A dict with the transaction details if the transaction was found. - Will add the transaction status to payload ('valid', 'undecided', - or 'backlog'). If no transaction with that `txid` was found it - returns `None` + A :class:`~.models.Transaction` instance if the transaction + was found, otherwise ``None``. + If :attr:`include_status` is ``True``, also returns the + transaction's status if the transaction was found. """ response, tx_status = None, None @@ -412,14 +412,14 @@ class Bigchain(object): return None def get_owned_ids(self, owner): - """Retrieve a list of `txids` that can we used has inputs. + """Retrieve a list of `txid`s that can be used as inputs. Args: owner (str): base58 encoded public key. Returns: - list (TransactionLink): list of `txid`s and `cid`s pointing to - another transaction's condition + :obj:`list` of TransactionLink: list of `txid`s and `cid`s + pointing to another transaction's condition """ # get all transactions in which owner is in the `owners_after` list @@ -587,11 +587,11 @@ class Bigchain(object): return block def vote(self, block_id, previous_block_id, decision, invalid_reason=None): - """Cast your vote on the block given the previous_block_hash and the decision (valid/invalid) - return the block to the updated in the database. + """Create a signed vote for a block given the + :attr:`previous_block_id` and the :attr:`decision` (valid/invalid). Args: - block_id (str): The id of the block to vote. + block_id (str): The id of the block to vote on. previous_block_id (str): The id of the previous block. decision (bool): Whether the block is valid or invalid. invalid_reason (Optional[str]): Reason the block is invalid diff --git a/bigchaindb/db/utils.py b/bigchaindb/db/utils.py index d51563d5..92e0fdd3 100644 --- a/bigchaindb/db/utils.py +++ b/bigchaindb/db/utils.py @@ -3,7 +3,7 @@ import time import logging -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions import rethinkdb as r import bigchaindb diff --git a/bigchaindb/models.py b/bigchaindb/models.py index 7ed81061..1334b2de 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -1,11 +1,11 @@ -from bigchaindb_common.crypto import hash_data, VerifyingKey, SigningKey -from bigchaindb_common.exceptions import (InvalidHash, InvalidSignature, +from bigchaindb.common.crypto import hash_data, VerifyingKey, SigningKey +from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature, OperationError, DoubleSpend, TransactionDoesNotExist, FulfillmentNotInValidBlock, AssetIdMismatch) -from bigchaindb_common.transaction import Transaction, Asset -from bigchaindb_common.util import gen_timestamp, serialize +from bigchaindb.common.transaction import Transaction, Asset +from bigchaindb.common.util import gen_timestamp, serialize class Asset(Asset): diff --git a/bigchaindb/pipelines/stale.py b/bigchaindb/pipelines/stale.py index 647ad2ae..cdc74aa6 100644 --- a/bigchaindb/pipelines/stale.py +++ b/bigchaindb/pipelines/stale.py @@ -26,8 +26,8 @@ class StaleTransactionMonitor: Args: timeout: how often to check for stale tx (in sec) backlog_reassign_delay: How stale a transaction should - be before reassignment (in sec). If supplied, overrides the - Bigchain default value. + be before reassignment (in sec). If supplied, overrides + the Bigchain default value. """ self.bigchain = Bigchain(backlog_reassign_delay=backlog_reassign_delay) self.timeout = timeout diff --git a/bigchaindb/pipelines/utils.py b/bigchaindb/pipelines/utils.py index 0a8dbcd1..26984500 100644 --- a/bigchaindb/pipelines/utils.py +++ b/bigchaindb/pipelines/utils.py @@ -13,16 +13,16 @@ logger = logging.getLogger(__name__) class ChangeFeed(Node): - """This class wraps a RethinkDB changefeed adding a `prefeed`. + """This class wraps a RethinkDB changefeed adding a ``prefeed``. - It extends the ``multipipes::Node`` class to make it pluggable in - other Pipelines instances, and it makes usage of ``self.outqueue`` - to output the data. + It extends :class:`multipipes.Node` to make it pluggable in other + Pipelines instances, and makes usage of ``self.outqueue`` to output + the data. A changefeed is a real time feed on inserts, updates, and deletes, and - it's volatile. This class is a helper to create changefeeds. Moreover - it provides a way to specify a `prefeed`, that is a set of data (iterable) - to output before the actual changefeed. + is volatile. This class is a helper to create changefeeds. Moreover, + it provides a way to specify a ``prefeed`` of iterable data to output + before the actual changefeed. """ INSERT = 1 @@ -35,8 +35,8 @@ class ChangeFeed(Node): Args: table (str): name of the table to listen to for changes. operation (int): can be ChangeFeed.INSERT, ChangeFeed.DELETE, or - ChangeFeed.UPDATE. Combining multiple operation is possible using - the bitwise ``|`` operator + ChangeFeed.UPDATE. Combining multiple operation is possible + with the bitwise ``|`` operator (e.g. ``ChangeFeed.INSERT | ChangeFeed.UPDATE``) prefeed (iterable): whatever set of data you want to be published first. diff --git a/bigchaindb/pipelines/vote.py b/bigchaindb/pipelines/vote.py index 3d79c265..6b12f55b 100644 --- a/bigchaindb/pipelines/vote.py +++ b/bigchaindb/pipelines/vote.py @@ -8,7 +8,7 @@ function. from collections import Counter from multipipes import Pipeline, Node -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions from bigchaindb.consensus import BaseConsensusRules from bigchaindb.models import Transaction, Block diff --git a/bigchaindb/util.py b/bigchaindb/util.py index d8fb13e3..272c7d67 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -3,8 +3,8 @@ import threading import queue import multiprocessing as mp -from bigchaindb_common import crypto -from bigchaindb_common.util import serialize +from bigchaindb.common import crypto +from bigchaindb.common.util import serialize class ProcessGroup(object): @@ -119,7 +119,7 @@ def condition_details_has_owner(condition_details, owner): def verify_vote_signature(voters, signed_vote): """Verify the signature of a vote - A valid vote should have been signed `owner_before` corresponding private key. + A valid vote should have been signed by a voter's private key. Args: voters (list): voters of the block that is under election diff --git a/bigchaindb/version.py b/bigchaindb/version.py index e3155206..569af116 100644 --- a/bigchaindb/version.py +++ b/bigchaindb/version.py @@ -1,2 +1,2 @@ -__version__ = '0.6.0' -__short_version__ = '0.6' +__version__ = '0.7.0.dev' +__short_version__ = '0.7.dev' diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index 6439ff4c..8780fda8 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -6,7 +6,7 @@ For more information please refer to the documentation on ReadTheDocs: from flask import current_app, request, Blueprint from flask_restful import Resource, Api -from bigchaindb_common.exceptions import InvalidHash, InvalidSignature +from bigchaindb.common.exceptions import InvalidHash, InvalidSignature import bigchaindb from bigchaindb.models import Transaction diff --git a/deploy-cluster-aws/write_keypairs_file.py b/deploy-cluster-aws/write_keypairs_file.py index 2037141c..da4fc1b1 100644 --- a/deploy-cluster-aws/write_keypairs_file.py +++ b/deploy-cluster-aws/write_keypairs_file.py @@ -1,5 +1,5 @@ """A Python 3 script to write a file with a specified number -of keypairs, using bigchaindb_common.crypto.generate_key_pair() +of keypairs, using bigchaindb.common.crypto.generate_key_pair() The written file is always named keypairs.py and it should be interpreted as a Python 2 script. @@ -16,7 +16,7 @@ Using the list in other Python scripts: import argparse -from bigchaindb_common import crypto +from bigchaindb.common import crypto # Parse the command-line arguments diff --git a/docs/README.md b/docs/README.md index 15879adc..5169f871 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,12 +10,14 @@ # How to Generate the HTML Version of the Long-Form Documentation -If you want to generate the HTML version of the long-form documentation on your local machine, you need to have Sphinx and some Sphinx-contrib packages installed. To do that, go to the BigchainDB `docs` directory (i.e. this directory) and do: +If you want to generate the HTML version of the long-form documentation on your local machine, you need to have Sphinx and some Sphinx-contrib packages installed. To do that, go to a subdirectory of `docs` (e.g. `docs/server`) and do: ```bash pip install -r requirements.txt ``` -You can then generate the HTML documentation by doing: + +You can then generate the HTML documentation _in that subdirectory_ by doing: ```bash make html ``` -The generated HTML documentation will be in the `docs/build/html` directory. You can view it by opening `docs/build/html/index.html` in your web browser. + +It should tell you where the generated documentation (HTML files) can be found. You can view it in your web browser. diff --git a/docs/Makefile b/docs/server/Makefile similarity index 100% rename from docs/Makefile rename to docs/server/Makefile diff --git a/docs/make.bat b/docs/server/make.bat similarity index 100% rename from docs/make.bat rename to docs/server/make.bat diff --git a/docs/requirements.txt b/docs/server/requirements.txt similarity index 100% rename from docs/requirements.txt rename to docs/server/requirements.txt diff --git a/docs/source/_static/Node-components.png b/docs/server/source/_static/Node-components.png similarity index 100% rename from docs/source/_static/Node-components.png rename to docs/server/source/_static/Node-components.png diff --git a/docs/source/_static/cc_escrow_execute_abort.png b/docs/server/source/_static/cc_escrow_execute_abort.png similarity index 100% rename from docs/source/_static/cc_escrow_execute_abort.png rename to docs/server/source/_static/cc_escrow_execute_abort.png diff --git a/docs/source/_static/cloud9.pdf b/docs/server/source/_static/cloud9.pdf similarity index 100% rename from docs/source/_static/cloud9.pdf rename to docs/server/source/_static/cloud9.pdf diff --git a/docs/source/_static/models_diagrams.odg b/docs/server/source/_static/models_diagrams.odg similarity index 100% rename from docs/source/_static/models_diagrams.odg rename to docs/server/source/_static/models_diagrams.odg diff --git a/docs/source/_static/monitoring_system_diagram.png b/docs/server/source/_static/monitoring_system_diagram.png similarity index 100% rename from docs/source/_static/monitoring_system_diagram.png rename to docs/server/source/_static/monitoring_system_diagram.png diff --git a/docs/source/_static/stories_3_assets.png b/docs/server/source/_static/stories_3_assets.png similarity index 100% rename from docs/source/_static/stories_3_assets.png rename to docs/server/source/_static/stories_3_assets.png diff --git a/docs/source/_static/tx_escrow_execute_abort.png b/docs/server/source/_static/tx_escrow_execute_abort.png similarity index 100% rename from docs/source/_static/tx_escrow_execute_abort.png rename to docs/server/source/_static/tx_escrow_execute_abort.png diff --git a/docs/source/_static/tx_multi_condition_multi_fulfillment_v1.png b/docs/server/source/_static/tx_multi_condition_multi_fulfillment_v1.png similarity index 100% rename from docs/source/_static/tx_multi_condition_multi_fulfillment_v1.png rename to docs/server/source/_static/tx_multi_condition_multi_fulfillment_v1.png diff --git a/docs/source/_static/tx_schematics.odg b/docs/server/source/_static/tx_schematics.odg similarity index 100% rename from docs/source/_static/tx_schematics.odg rename to docs/server/source/_static/tx_schematics.odg diff --git a/docs/source/_static/tx_single_condition_single_fulfillment_v1.png b/docs/server/source/_static/tx_single_condition_single_fulfillment_v1.png similarity index 100% rename from docs/source/_static/tx_single_condition_single_fulfillment_v1.png rename to docs/server/source/_static/tx_single_condition_single_fulfillment_v1.png diff --git a/docs/source/appendices/aws-setup.md b/docs/server/source/appendices/aws-setup.md similarity index 100% rename from docs/source/appendices/aws-setup.md rename to docs/server/source/appendices/aws-setup.md diff --git a/docs/source/appendices/consensus.rst b/docs/server/source/appendices/consensus.rst similarity index 100% rename from docs/source/appendices/consensus.rst rename to docs/server/source/appendices/consensus.rst diff --git a/docs/source/appendices/cryptography.md b/docs/server/source/appendices/cryptography.md similarity index 100% rename from docs/source/appendices/cryptography.md rename to docs/server/source/appendices/cryptography.md diff --git a/docs/source/appendices/example-rethinkdb-storage-setups.md b/docs/server/source/appendices/example-rethinkdb-storage-setups.md similarity index 100% rename from docs/source/appendices/example-rethinkdb-storage-setups.md rename to docs/server/source/appendices/example-rethinkdb-storage-setups.md diff --git a/docs/source/appendices/firewall-notes.md b/docs/server/source/appendices/firewall-notes.md similarity index 100% rename from docs/source/appendices/firewall-notes.md rename to docs/server/source/appendices/firewall-notes.md diff --git a/docs/source/appendices/index.rst b/docs/server/source/appendices/index.rst similarity index 100% rename from docs/source/appendices/index.rst rename to docs/server/source/appendices/index.rst diff --git a/docs/source/appendices/install-latest-pip.md b/docs/server/source/appendices/install-latest-pip.md similarity index 100% rename from docs/source/appendices/install-latest-pip.md rename to docs/server/source/appendices/install-latest-pip.md diff --git a/docs/source/appendices/install-os-level-deps.md b/docs/server/source/appendices/install-os-level-deps.md similarity index 100% rename from docs/source/appendices/install-os-level-deps.md rename to docs/server/source/appendices/install-os-level-deps.md diff --git a/docs/source/appendices/install-with-lxd.md b/docs/server/source/appendices/install-with-lxd.md similarity index 100% rename from docs/source/appendices/install-with-lxd.md rename to docs/server/source/appendices/install-with-lxd.md diff --git a/docs/source/appendices/json-serialization.md b/docs/server/source/appendices/json-serialization.md similarity index 100% rename from docs/source/appendices/json-serialization.md rename to docs/server/source/appendices/json-serialization.md diff --git a/docs/source/appendices/licenses.md b/docs/server/source/appendices/licenses.md similarity index 100% rename from docs/source/appendices/licenses.md rename to docs/server/source/appendices/licenses.md diff --git a/docs/source/appendices/ntp-notes.md b/docs/server/source/appendices/ntp-notes.md similarity index 100% rename from docs/source/appendices/ntp-notes.md rename to docs/server/source/appendices/ntp-notes.md diff --git a/docs/source/appendices/pipelines.rst b/docs/server/source/appendices/pipelines.rst similarity index 100% rename from docs/source/appendices/pipelines.rst rename to docs/server/source/appendices/pipelines.rst diff --git a/docs/source/appendices/run-with-docker.md b/docs/server/source/appendices/run-with-docker.md similarity index 100% rename from docs/source/appendices/run-with-docker.md rename to docs/server/source/appendices/run-with-docker.md diff --git a/docs/source/appendices/the-Bigchain-class.rst b/docs/server/source/appendices/the-Bigchain-class.rst similarity index 100% rename from docs/source/appendices/the-Bigchain-class.rst rename to docs/server/source/appendices/the-Bigchain-class.rst diff --git a/docs/source/cloud-deployment-starter-templates/index.rst b/docs/server/source/cloud-deployment-starter-templates/index.rst similarity index 100% rename from docs/source/cloud-deployment-starter-templates/index.rst rename to docs/server/source/cloud-deployment-starter-templates/index.rst diff --git a/docs/source/cloud-deployment-starter-templates/template-ansible.md b/docs/server/source/cloud-deployment-starter-templates/template-ansible.md similarity index 100% rename from docs/source/cloud-deployment-starter-templates/template-ansible.md rename to docs/server/source/cloud-deployment-starter-templates/template-ansible.md diff --git a/docs/server/source/cloud-deployment-starter-templates/template-azure.md b/docs/server/source/cloud-deployment-starter-templates/template-azure.md new file mode 100644 index 00000000..73b015ca --- /dev/null +++ b/docs/server/source/cloud-deployment-starter-templates/template-azure.md @@ -0,0 +1,52 @@ +# Template: Node Deployment on Azure + +If you didn't read the introduction to the [cloud deployment starter templates](index.html), please do that now. The main point is that they're not for deploying a production node; they can be used as a starting point. + +One can deploy a BigchainDB node on Azure using the template in [Microsoft's azure-quickstart-templates repository on GitHub](https://github.com/Azure/azure-quickstart-templates): + +1. Go to [the /blockchain subdirectory in that repository](https://github.com/Azure/azure-quickstart-templates/tree/master/blockchain). + +2. Click the button labelled **Deploy to Azure**. + +3. If you're not already logged in to Microsoft Azure, then you'll be prompted to login. If you don't have an account, then you'll have to create one. + +4. One you are logged in to the Microsoft Azure Portal, you should be taken to a form titled **Blockchain Template**. Below there are some notes to help with filling in that form. + +5. Deployment takes a few minutes. You can follow the notifications by clicking the bell icon at the top of the screen. When done, you should see a notification saying "Deployment to resource group '[your resource group name]' was successful." The install script (`bigchaindb.sh`) installed RethinkDB, configured it using the default RethinkDB config file, and ran it. It also used pip to install [the latest `bigchaindb` from PyPI](https://pypi.python.org/pypi/BigchainDB). + +6. Find out the public IP address of the virtual machine in the Azure Portal. Example: `40.69.87.250` + +7. ssh in to the virtual machine at that IP address, e.g. `ssh adminusername@40.69.87.250` (where `adminusername` should be replaced by the Admin Username you entered into the form, and `40.69.87.250` should be replaced by the IP address you found in the last step). + +8. You should be prompted for a password. Enter the Admin Password you entered into the form. + +9. Configure BigchainDB using the default BigchainDB settings: `bigchaindb -y configure` + +10. Run BigchainDB: `bigchaindb start` + +BigchainDB should now be running on the Azure VM. + +Remember to shut everything down when you're done (via the Azure Portal), because it generally costs money to run stuff on Azure. + + +## Notes on the Blockchain Template Form Fields + +**Resource group** - You can use an existing resource group (if you have one) or create a new one named whatever you like, but avoid using fancy characters in the name because Azure might have problems if you do. + +**Location** is the Microsoft Azure data center where you want the BigchainDB node to run. Pick one close to where you are located. + +**Vm Dns Prefix** - Once your virtual machine (VM) is deployed, it will have a public IP address and a DNS name (hostname) something like `DNSprefix.northeurope.cloudapp.azure.com`. The `DNSprefix` will be whatever you enter into this field. + +You can use whatever **Admin Username** and **Admin Password** you like (provided you don't get too fancy). It will complain if your password is too simple. You'll need these later to ssh into the VM. + +**Blockchain Software** - Select `bigchaindb`. + +For **Vm Size**, select `Standard_D1_v2` or better. + +**\_artifacts Location** - Leave this alone. + +**\_artifacts Location Sas Token** - Leave this alone (blank). + +Don't forget to scroll down and check the box to agree to the terms and conditions. + +Once you've finished the form, click the button labelled **Purchase**. (Generally speaking, it costs money to run stuff on Azure.) diff --git a/docs/source/cloud-deployment-starter-templates/template-terraform-aws.md b/docs/server/source/cloud-deployment-starter-templates/template-terraform-aws.md similarity index 100% rename from docs/source/cloud-deployment-starter-templates/template-terraform-aws.md rename to docs/server/source/cloud-deployment-starter-templates/template-terraform-aws.md diff --git a/docs/source/clusters-feds/aws-testing-cluster.md b/docs/server/source/clusters-feds/aws-testing-cluster.md similarity index 100% rename from docs/source/clusters-feds/aws-testing-cluster.md rename to docs/server/source/clusters-feds/aws-testing-cluster.md diff --git a/docs/source/clusters-feds/backup.md b/docs/server/source/clusters-feds/backup.md similarity index 100% rename from docs/source/clusters-feds/backup.md rename to docs/server/source/clusters-feds/backup.md diff --git a/docs/source/clusters-feds/index.rst b/docs/server/source/clusters-feds/index.rst similarity index 100% rename from docs/source/clusters-feds/index.rst rename to docs/server/source/clusters-feds/index.rst diff --git a/docs/source/clusters-feds/monitoring.md b/docs/server/source/clusters-feds/monitoring.md similarity index 100% rename from docs/source/clusters-feds/monitoring.md rename to docs/server/source/clusters-feds/monitoring.md diff --git a/docs/source/clusters-feds/set-up-a-federation.md b/docs/server/source/clusters-feds/set-up-a-federation.md similarity index 100% rename from docs/source/clusters-feds/set-up-a-federation.md rename to docs/server/source/clusters-feds/set-up-a-federation.md diff --git a/docs/source/conf.py b/docs/server/source/conf.py similarity index 98% rename from docs/source/conf.py rename to docs/server/source/conf.py index e429154c..640e3273 100644 --- a/docs/source/conf.py +++ b/docs/server/source/conf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# BigchainDB documentation build configuration file, created by +# BigchainDB Server documentation build configuration file, created by # sphinx-quickstart on Tue Jan 19 14:42:58 2016. # # This file is execfile()d with the current directory set to its @@ -32,7 +32,7 @@ import sphinx_rtd_theme # get version _version = {} -with open('../../bigchaindb/version.py') as fp: +with open('../../../bigchaindb/version.py') as fp: exec(fp.read(), _version) diff --git a/docs/source/dev-and-test/index.rst b/docs/server/source/dev-and-test/index.rst similarity index 100% rename from docs/source/dev-and-test/index.rst rename to docs/server/source/dev-and-test/index.rst diff --git a/docs/source/dev-and-test/running-unit-tests.md b/docs/server/source/dev-and-test/running-unit-tests.md similarity index 100% rename from docs/source/dev-and-test/running-unit-tests.md rename to docs/server/source/dev-and-test/running-unit-tests.md diff --git a/docs/source/dev-and-test/setup-run-node.md b/docs/server/source/dev-and-test/setup-run-node.md similarity index 100% rename from docs/source/dev-and-test/setup-run-node.md rename to docs/server/source/dev-and-test/setup-run-node.md diff --git a/docs/source/drivers-clients/example-apps.rst b/docs/server/source/drivers-clients/example-apps.rst similarity index 100% rename from docs/source/drivers-clients/example-apps.rst rename to docs/server/source/drivers-clients/example-apps.rst diff --git a/docs/source/drivers-clients/http-client-server-api.rst b/docs/server/source/drivers-clients/http-client-server-api.rst similarity index 100% rename from docs/source/drivers-clients/http-client-server-api.rst rename to docs/server/source/drivers-clients/http-client-server-api.rst diff --git a/docs/source/drivers-clients/index.rst b/docs/server/source/drivers-clients/index.rst similarity index 100% rename from docs/source/drivers-clients/index.rst rename to docs/server/source/drivers-clients/index.rst diff --git a/docs/source/drivers-clients/python-driver.md b/docs/server/source/drivers-clients/python-driver.md similarity index 100% rename from docs/source/drivers-clients/python-driver.md rename to docs/server/source/drivers-clients/python-driver.md diff --git a/docs/source/drivers-clients/python-server-api-examples.md b/docs/server/source/drivers-clients/python-server-api-examples.md similarity index 100% rename from docs/source/drivers-clients/python-server-api-examples.md rename to docs/server/source/drivers-clients/python-server-api-examples.md diff --git a/docs/source/index.rst b/docs/server/source/index.rst similarity index 100% rename from docs/source/index.rst rename to docs/server/source/index.rst diff --git a/docs/source/introduction.md b/docs/server/source/introduction.md similarity index 100% rename from docs/source/introduction.md rename to docs/server/source/introduction.md diff --git a/docs/source/nodes/index.rst b/docs/server/source/nodes/index.rst similarity index 100% rename from docs/source/nodes/index.rst rename to docs/server/source/nodes/index.rst diff --git a/docs/source/nodes/node-assumptions.md b/docs/server/source/nodes/node-assumptions.md similarity index 100% rename from docs/source/nodes/node-assumptions.md rename to docs/server/source/nodes/node-assumptions.md diff --git a/docs/source/nodes/node-components.md b/docs/server/source/nodes/node-components.md similarity index 100% rename from docs/source/nodes/node-components.md rename to docs/server/source/nodes/node-components.md diff --git a/docs/source/nodes/node-requirements.md b/docs/server/source/nodes/node-requirements.md similarity index 100% rename from docs/source/nodes/node-requirements.md rename to docs/server/source/nodes/node-requirements.md diff --git a/docs/source/nodes/setup-run-node.md b/docs/server/source/nodes/setup-run-node.md similarity index 100% rename from docs/source/nodes/setup-run-node.md rename to docs/server/source/nodes/setup-run-node.md diff --git a/docs/source/quickstart.md b/docs/server/source/quickstart.md similarity index 100% rename from docs/source/quickstart.md rename to docs/server/source/quickstart.md diff --git a/docs/source/release-notes.md b/docs/server/source/release-notes.md similarity index 100% rename from docs/source/release-notes.md rename to docs/server/source/release-notes.md diff --git a/docs/source/server-reference/bigchaindb-cli.md b/docs/server/source/server-reference/bigchaindb-cli.md similarity index 100% rename from docs/source/server-reference/bigchaindb-cli.md rename to docs/server/source/server-reference/bigchaindb-cli.md diff --git a/docs/source/server-reference/configuration.md b/docs/server/source/server-reference/configuration.md similarity index 100% rename from docs/source/server-reference/configuration.md rename to docs/server/source/server-reference/configuration.md diff --git a/docs/source/server-reference/index.rst b/docs/server/source/server-reference/index.rst similarity index 100% rename from docs/source/server-reference/index.rst rename to docs/server/source/server-reference/index.rst diff --git a/docs/source/topic-guides/index.rst b/docs/server/source/topic-guides/index.rst similarity index 100% rename from docs/source/topic-guides/index.rst rename to docs/server/source/topic-guides/index.rst diff --git a/docs/source/topic-guides/models.md b/docs/server/source/topic-guides/models.md similarity index 100% rename from docs/source/topic-guides/models.md rename to docs/server/source/topic-guides/models.md diff --git a/docs/source/cloud-deployment-starter-templates/template-azure.md b/docs/source/cloud-deployment-starter-templates/template-azure.md deleted file mode 100644 index 58e094d3..00000000 --- a/docs/source/cloud-deployment-starter-templates/template-azure.md +++ /dev/null @@ -1,5 +0,0 @@ -# Template: Node Deployment on Azure - -If you didn't read the introduction to the [cloud deployment starter templates](index.html), please do that now. The main point is that they're not for deploying a production node; they can be used as a starting point. - -This page is a placeholder. diff --git a/setup.py b/setup.py index 13c13e93..67a367f6 100644 --- a/setup.py +++ b/setup.py @@ -103,7 +103,6 @@ setup( 'requests~=2.9', 'gunicorn~=19.0', 'multipipes~=0.1.0', - 'bigchaindb-common==0.0.6', ], setup_requires=['pytest-runner'], tests_require=tests_require, diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 64da4c11..e18684c5 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -76,7 +76,7 @@ def test_validate_bad_asset_creation(b, user_vk): @pytest.mark.usefixtures('inputs') def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk): - from bigchaindb_common.exceptions import AssetIdMismatch + from bigchaindb.common.exceptions import AssetIdMismatch from bigchaindb.models import Transaction tx_create = b.get_owned_ids(user_vk).pop() @@ -121,7 +121,7 @@ def test_get_asset_id_transfer_transaction(b, user_vk, user_sk): def test_asset_id_mismatch(b, user_vk): from bigchaindb.models import Transaction, Asset - from bigchaindb_common.exceptions import AssetIdMismatch + from bigchaindb.common.exceptions import AssetIdMismatch tx1 = Transaction.create([b.me], [user_vk]) tx2 = Transaction.create([b.me], [user_vk]) diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/common/conftest.py b/tests/common/conftest.py new file mode 100644 index 00000000..8b1a47db --- /dev/null +++ b/tests/common/conftest.py @@ -0,0 +1,162 @@ +import pytest + + +USER_PRIVATE_KEY = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie' +USER_PUBLIC_KEY = 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE' + +USER2_PRIVATE_KEY = 'F86PQPiqMTwM2Qi2Sda3U4Vdh3AgadMdX3KNVsu5wNJr' +USER2_PUBLIC_KEY = 'GDxwMFbwdATkQELZbMfW8bd9hbNYMZLyVXA3nur2aNbE' + +USER3_PRIVATE_KEY = '4rNQFzWQbVwuTiDVxwuFMvLG5zd8AhrQKCtVovBvcYsB' +USER3_PUBLIC_KEY = 'Gbrg7JtxdjedQRmr81ZZbh1BozS7fBW88ZyxNDy7WLNC' + + +CC_FULFILLMENT_URI = 'cf:0:' +CC_CONDITION_URI = 'cc:0:3:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:0' + +DATA = { + 'msg': 'Hello BigchainDB!' +} +DATA_ID = '872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8' + + +@pytest.fixture +def user_priv(): + return USER_PRIVATE_KEY + + +@pytest.fixture +def user_pub(): + return USER_PUBLIC_KEY + + +@pytest.fixture +def user2_priv(): + return USER2_PRIVATE_KEY + + +@pytest.fixture +def user2_pub(): + return USER2_PUBLIC_KEY + + +@pytest.fixture +def user3_priv(): + return USER3_PRIVATE_KEY + + +@pytest.fixture +def user3_pub(): + return USER3_PUBLIC_KEY + + +@pytest.fixture +def ffill_uri(): + return CC_FULFILLMENT_URI + + +@pytest.fixture +def cond_uri(): + return CC_CONDITION_URI + + +@pytest.fixture +def user_Ed25519(user_pub): + from cryptoconditions import Ed25519Fulfillment + return Ed25519Fulfillment(public_key=user_pub) + + +@pytest.fixture +def user_user2_threshold(user_pub, user2_pub): + from cryptoconditions import (ThresholdSha256Fulfillment, + Ed25519Fulfillment) + user_pub_keys = [user_pub, user2_pub] + threshold = ThresholdSha256Fulfillment(threshold=len(user_pub_keys)) + for user_pub in user_pub_keys: + threshold.add_subfulfillment(Ed25519Fulfillment(public_key=user_pub)) + return threshold + + +@pytest.fixture +def user2_Ed25519(user2_pub): + from cryptoconditions import Ed25519Fulfillment + return Ed25519Fulfillment(public_key=user2_pub) + + +@pytest.fixture +def user_ffill(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Fulfillment + return Fulfillment(user_Ed25519, [user_pub]) + + +@pytest.fixture +def user2_ffill(user2_Ed25519, user2_pub): + from bigchaindb.common.transaction import Fulfillment + return Fulfillment(user2_Ed25519, [user2_pub]) + + +@pytest.fixture +def user_user2_threshold_cond(user_user2_threshold, user_pub, user2_pub): + from bigchaindb.common.transaction import Condition + return Condition(user_user2_threshold, [user_pub, user2_pub]) + + +@pytest.fixture +def user_user2_threshold_ffill(user_user2_threshold, user_pub, user2_pub): + from bigchaindb.common.transaction import Fulfillment + return Fulfillment(user_user2_threshold, [user_pub, user2_pub]) + + +@pytest.fixture +def user_cond(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Condition + return Condition(user_Ed25519, [user_pub]) + + +@pytest.fixture +def user2_cond(user2_Ed25519, user2_pub): + from bigchaindb.common.transaction import Condition + return Condition(user2_Ed25519, [user2_pub]) + + +@pytest.fixture +def data(): + return DATA + + +@pytest.fixture +def data_id(): + return DATA_ID + + +@pytest.fixture +def metadata(data, data_id): + from bigchaindb.common.transaction import Metadata + return Metadata(data, data_id) + + +@pytest.fixture +def utx(user_ffill, user_cond): + from bigchaindb.common.transaction import Transaction, Asset + return Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) + + +@pytest.fixture +def tx(utx, user_priv): + return utx.sign([user_priv]) + + +@pytest.fixture +def transfer_utx(user_cond, user2_cond, utx): + from bigchaindb.common.transaction import (Fulfillment, TransactionLink, + Transaction, Asset) + user_cond = user_cond.to_dict() + ffill = Fulfillment(utx.conditions[0].fulfillment, + user_cond['owners_after'], + TransactionLink(utx.id, 0)) + return Transaction('TRANSFER', Asset(), [ffill], [user2_cond]) + + +@pytest.fixture +def transfer_tx(transfer_utx, user_priv): + return transfer_utx.sign([user_priv]) diff --git a/tests/common/test_asset.py b/tests/common/test_asset.py new file mode 100644 index 00000000..edfbcb5f --- /dev/null +++ b/tests/common/test_asset.py @@ -0,0 +1,80 @@ +from pytest import raises + + +def test_asset_default_values(): + from bigchaindb.common.transaction import Asset + + asset = Asset() + assert asset.data is None + assert asset.data_id + assert asset.divisible is False + assert asset.updatable is False + assert asset.refillable is False + + +def test_asset_creation_with_data(data): + from bigchaindb.common.transaction import Asset + + asset = Asset(data) + assert asset.data == data + + +def test_asset_invalid_asset_initialization(): + from bigchaindb.common.transaction import Asset + + with raises(TypeError): + Asset(data='some wrong type') + with raises(TypeError): + Asset(divisible=1) + with raises(TypeError): + Asset(refillable=1) + with raises(TypeError): + Asset(updatable=1) + + +def test_invalid_asset_comparison(data, data_id): + from bigchaindb.common.transaction import Asset + + assert Asset(data, data_id) != 'invalid comparison' + + +def test_asset_serialization(data, data_id): + from bigchaindb.common.transaction import Asset + + expected = { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + asset = Asset(data, data_id) + assert asset.to_dict() == expected + + +def test_asset_deserialization(data, data_id): + from bigchaindb.common.transaction import Asset + + asset_dict = { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + asset = Asset.from_dict(asset_dict) + expected = Asset(data, data_id) + assert asset == expected + + +def test_validate_asset(): + from bigchaindb.common.transaction import 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') diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py new file mode 100644 index 00000000..5f2d58fb --- /dev/null +++ b/tests/common/test_transaction.py @@ -0,0 +1,1106 @@ +from pytest import raises, mark + + +def test_fulfillment_serialization(ffill_uri, user_pub): + from bigchaindb.common.transaction import Fulfillment + from cryptoconditions import Fulfillment as CCFulfillment + + expected = { + 'owners_before': [user_pub], + 'fulfillment': ffill_uri, + 'input': None, + } + ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) + assert ffill.to_dict() == expected + + +def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): + from bigchaindb.common.transaction import Fulfillment + from cryptoconditions import Fulfillment as CCFulfillment + + expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) + ffill = { + 'owners_before': [user_pub], + 'fulfillment': ffill_uri, + 'input': None, + } + ffill = Fulfillment.from_dict(ffill) + + assert ffill == expected + + +def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): + from bigchaindb.common.transaction import Fulfillment + + ffill = { + 'owners_before': [user_pub], + 'fulfillment': None, + 'input': None, + } + with raises(TypeError): + Fulfillment.from_dict(ffill) + + +def test_fulfillment_deserialization_with_invalid_fulfillment_uri(user_pub): + from bigchaindb.common.exceptions import InvalidSignature + from bigchaindb.common.transaction import Fulfillment + + ffill = { + 'owners_before': [user_pub], + 'fulfillment': 'an invalid fulfillment', + 'input': None, + } + with raises(InvalidSignature): + Fulfillment.from_dict(ffill) + + +def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri, + user_pub): + from bigchaindb.common.transaction import Fulfillment + from cryptoconditions import Fulfillment as CCFulfillment + + expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) + ffill = { + 'owners_before': [user_pub], + 'fulfillment': CCFulfillment.from_uri(ffill_uri), + 'input': None, + } + ffill = Fulfillment.from_dict(ffill) + + assert ffill == expected + + +def test_condition_serialization(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Condition + + expected = { + 'condition': { + 'uri': user_Ed25519.condition_uri, + 'details': user_Ed25519.to_dict(), + }, + 'owners_after': [user_pub], + 'amount': 1, + } + + cond = Condition(user_Ed25519, [user_pub], 1) + + assert cond.to_dict() == expected + + +def test_condition_deserialization(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Condition + + expected = Condition(user_Ed25519, [user_pub], 1) + cond = { + 'condition': { + 'uri': user_Ed25519.condition_uri, + 'details': user_Ed25519.to_dict() + }, + 'owners_after': [user_pub], + 'amount': 1, + } + cond = Condition.from_dict(cond) + + assert cond == expected + + +def test_condition_hashlock_serialization(): + from bigchaindb.common.transaction import Condition + from cryptoconditions import PreimageSha256Fulfillment + + secret = b'wow much secret' + hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri + + expected = { + 'condition': { + 'uri': hashlock, + }, + 'owners_after': None, + 'amount': 1, + } + cond = Condition(hashlock, amount=1) + + assert cond.to_dict() == expected + + +def test_condition_hashlock_deserialization(): + from bigchaindb.common.transaction import Condition + from cryptoconditions import PreimageSha256Fulfillment + + secret = b'wow much secret' + hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri + expected = Condition(hashlock, amount=1) + + cond = { + 'condition': { + 'uri': hashlock + }, + 'owners_after': None, + 'amount': 1, + } + cond = Condition.from_dict(cond) + + assert cond == expected + + +def test_invalid_condition_initialization(cond_uri, user_pub): + from bigchaindb.common.transaction import Condition + + with raises(TypeError): + Condition(cond_uri, user_pub) + + +def test_generate_conditions_split_half_recursive(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=2) + expected.add_subfulfillment(expected_simple1) + expected_threshold = ThresholdSha256Fulfillment(threshold=2) + 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]]) + 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)) + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_split_half_single_owner(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=2) + expected_threshold = ThresholdSha256Fulfillment(threshold=2) + expected_threshold.add_subfulfillment(expected_simple2) + expected_threshold.add_subfulfillment(expected_simple3) + expected.add_subfulfillment(expected_threshold) + expected.add_subfulfillment(expected_simple1) + + cond = Condition.generate([[expected_simple2, user3_pub], user_pub]) + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_flat_ownage(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=3) + expected.add_subfulfillment(expected_simple1) + expected.add_subfulfillment(expected_simple2) + expected.add_subfulfillment(expected_simple3) + + cond = Condition.generate([user_pub, user2_pub, expected_simple3]) + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_single_owner(user_pub): + from bigchaindb.common.transaction import Condition + from cryptoconditions import Ed25519Fulfillment + + expected = Ed25519Fulfillment(public_key=user_pub) + cond = Condition.generate([user_pub]) + + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_single_owner_with_condition(user_pub): + from bigchaindb.common.transaction import Condition + from cryptoconditions import Ed25519Fulfillment + + expected = Ed25519Fulfillment(public_key=user_pub) + cond = Condition.generate([expected]) + + 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([]) + with raises(TypeError): + Condition.generate('not a list') + with raises(ValueError): + Condition.generate([[user_pub, [user2_pub, [user3_pub]]]]) + with raises(ValueError): + Condition.generate([[user_pub]]) + + +def test_invalid_transaction_initialization(): + from bigchaindb.common.transaction import Transaction, Asset + + with raises(ValueError): + Transaction(operation='invalid operation', asset=Asset()) + with raises(TypeError): + Transaction(operation='CREATE', asset='invalid asset') + with raises(TypeError): + Transaction( + operation='CREATE', + asset=Asset(), + conditions='invalid conditions' + ) + with raises(TypeError): + Transaction( + operation='CREATE', + asset=Asset(), + conditions=[], + fulfillments='invalid fulfillments' + ) + with raises(TypeError): + Transaction( + operation='CREATE', + asset=Asset(), + conditions=[], + fulfillments=[], + metadata='invalid metadata' + ) + + +def test_create_default_asset_on_tx_initialization(): + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, None) + expected = Asset() + asset = tx.asset + + expected.data_id = None + asset.data_id = None + assert asset == expected + + +def test_transaction_serialization(user_ffill, user_cond, data, data_id): + from bigchaindb.common.transaction import Transaction, Asset + + tx_id = 'l0l' + timestamp = '66666666666' + + expected = { + 'id': tx_id, + 'version': Transaction.VERSION, + 'transaction': { + # NOTE: This test assumes that Fulfillments and Conditions can + # successfully be serialized + 'fulfillments': [user_ffill.to_dict(0)], + 'conditions': [user_cond.to_dict(0)], + 'operation': Transaction.CREATE, + 'timestamp': timestamp, + 'metadata': None, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + } + } + + tx = Transaction(Transaction.CREATE, Asset(data, data_id), [user_ffill], + [user_cond]) + tx_dict = tx.to_dict() + tx_dict['id'] = tx_id + tx_dict['transaction']['asset']['id'] = data_id + tx_dict['transaction']['timestamp'] = timestamp + + assert tx_dict == expected + + +def test_transaction_deserialization(user_ffill, user_cond, data, data_id): + from bigchaindb.common.transaction import Transaction, Asset + + timestamp = '66666666666' + + expected_asset = Asset(data, data_id) + expected = Transaction(Transaction.CREATE, expected_asset, [user_ffill], + [user_cond], None, timestamp, Transaction.VERSION) + + tx = { + 'version': Transaction.VERSION, + 'transaction': { + # NOTE: This test assumes that Fulfillments and Conditions can + # successfully be serialized + 'fulfillments': [user_ffill.to_dict()], + 'conditions': [user_cond.to_dict()], + 'operation': Transaction.CREATE, + 'timestamp': timestamp, + 'metadata': None, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + } + } + tx_no_signatures = Transaction._remove_signatures(tx) + tx['id'] = Transaction._to_hash(Transaction._to_str(tx_no_signatures)) + tx = Transaction.from_dict(tx) + + assert tx == expected + + +def test_tx_serialization_with_incorrect_hash(utx): + from bigchaindb.common.transaction import Transaction + from bigchaindb.common.exceptions import InvalidHash + + utx_dict = utx.to_dict() + utx_dict['id'] = 'abc' + with raises(InvalidHash): + Transaction.from_dict(utx_dict) + utx_dict.pop('id') + with raises(InvalidHash): + Transaction.from_dict(utx_dict) + utx_dict['id'] = [] + with raises(InvalidHash): + Transaction.from_dict(utx_dict) + + +def test_invalid_fulfillment_initialization(user_ffill, user_pub): + from bigchaindb.common.transaction import Fulfillment + + with raises(TypeError): + Fulfillment(user_ffill, user_pub) + with raises(TypeError): + Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') + + +def test_invalid_metadata_initialization(): + from bigchaindb.common.transaction import Metadata + + with raises(TypeError): + Metadata([]) + + +def test_metadata_serialization(data, data_id): + from bigchaindb.common.transaction import Metadata + + expected = { + 'data': data, + 'id': data_id, + } + metadata = Metadata(data, data_id) + + assert metadata.to_dict() == expected + + +def test_metadata_deserialization(data, data_id): + from bigchaindb.common.transaction import Metadata + + expected = Metadata(data, data_id) + metadata = Metadata.from_dict({'data': data, 'id': data_id}) + + assert metadata == expected + + +def test_transaction_link_serialization(): + from bigchaindb.common.transaction import TransactionLink + + tx_id = 'a transaction id' + expected = { + 'txid': tx_id, + 'cid': 0, + } + tx_link = TransactionLink(tx_id, 0) + + assert tx_link.to_dict() == expected + + +def test_transaction_link_serialization_with_empty_payload(): + from bigchaindb.common.transaction import TransactionLink + + expected = None + tx_link = TransactionLink() + + assert tx_link.to_dict() == expected + + +def test_transaction_link_deserialization(): + from bigchaindb.common.transaction import TransactionLink + + tx_id = 'a transaction id' + expected = TransactionLink(tx_id, 0) + tx_link = { + 'txid': tx_id, + 'cid': 0, + } + tx_link = TransactionLink.from_dict(tx_link) + + assert tx_link == expected + + +def test_transaction_link_deserialization_with_empty_payload(): + from bigchaindb.common.transaction import TransactionLink + + expected = TransactionLink() + tx_link = TransactionLink.from_dict(None) + + assert tx_link == expected + + +def test_cast_transaction_link_to_boolean(): + from bigchaindb.common.transaction import TransactionLink + + assert bool(TransactionLink()) is False + assert bool(TransactionLink('a', None)) is False + assert bool(TransactionLink(None, 'b')) is False + assert bool(TransactionLink('a', 'b')) is True + assert bool(TransactionLink(False, False)) is True + + +def test_add_fulfillment_to_tx(user_ffill): + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset(), [], []) + tx.add_fulfillment(user_ffill) + + assert len(tx.fulfillments) == 1 + + +def test_add_fulfillment_to_tx_with_invalid_parameters(): + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset()) + with raises(TypeError): + tx.add_fulfillment('somewronginput') + + +def test_add_condition_to_tx(user_cond): + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset()) + tx.add_condition(user_cond) + + assert len(tx.conditions) == 1 + + +def test_add_condition_to_tx_with_invalid_parameters(): + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset(), [], []) + with raises(TypeError): + tx.add_condition('somewronginput') + + +def test_sign_with_invalid_parameters(utx, user_priv): + with raises(TypeError): + utx.sign(None) + with raises(TypeError): + utx.sign(user_priv) + + +def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): + from copy import deepcopy + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) + expected = deepcopy(user_cond) + expected.fulfillment.sign(str(tx).encode(), SigningKey(user_priv)) + tx.sign([user_priv]) + + assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + expected.fulfillment.serialize_uri() + assert tx.fulfillments_valid() is True + + +def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, + user_ffill): + from bigchaindb.common.exceptions import KeypairMismatchException + + with raises(KeypairMismatchException): + invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'} + utx._sign_simple_signature_fulfillment(user_ffill, + 0, + 'somemessage', + invalid_key_pair) + + +def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, + user3_pub, user3_priv): + from bigchaindb.common.exceptions import KeypairMismatchException + + with raises(KeypairMismatchException): + utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, + 0, + 'somemessage', + {user3_pub: user3_priv}) + with raises(KeypairMismatchException): + user_user2_threshold_ffill.owners_before = ['somewrongvalue'] + utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, + 0, + 'somemessage', + None) + + +def test_validate_fulfillment_with_invalid_parameters(utx): + from bigchaindb.common.transaction import Transaction + + input_conditions = [cond.fulfillment.condition_uri for cond + in utx.conditions] + tx_dict = utx.to_dict() + tx_dict = Transaction._remove_signatures(tx_dict) + tx_serialized = Transaction._to_str(tx_dict) + assert utx._fulfillment_valid(utx.fulfillments[0], + tx_serialized, + input_conditions) is False + + +def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): + from copy import deepcopy + + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset(), + [user_ffill, deepcopy(user_ffill)], + [user_ffill, 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, + SigningKey(user_priv)) + expected_second_bytes = str(expected_second).encode() + expected_second.fulfillments[0].fulfillment.sign(expected_second_bytes, + SigningKey(user_priv)) + tx.sign([user_priv]) + + assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + expected_first.fulfillments[0].fulfillment.serialize_uri() + assert tx.fulfillments[1].to_dict()['fulfillment'] == \ + expected_second.fulfillments[0].fulfillment.serialize_uri() + assert tx.fulfillments_valid() is True + + +def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, + user_user2_threshold_cond, + user_pub, + user2_pub, + user_priv, + user2_priv): + from copy import deepcopy + + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset + + tx = Transaction(Transaction.CREATE, Asset(), [user_user2_threshold_ffill], + [user_user2_threshold_cond]) + expected = deepcopy(user_user2_threshold_cond) + expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(), + SigningKey(user_priv)) + expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(), + SigningKey(user2_priv)) + tx.sign([user_priv, user2_priv]) + + assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + expected.fulfillment.serialize_uri() + assert tx.fulfillments_valid() is True + + +def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, + user_priv, user2_pub, + user2_priv, user3_pub, + user3_priv): + from copy import deepcopy + from bigchaindb.common.transaction import (Transaction, TransactionLink, + Fulfillment, Condition, Asset) + from cryptoconditions import Ed25519Fulfillment + + tx = Transaction(Transaction.CREATE, Asset(), + [user_ffill, deepcopy(user_ffill)], + [user_cond, deepcopy(user_cond)]) + tx.sign([user_priv]) + + fulfillments = [Fulfillment(cond.fulfillment, cond.owners_after, + TransactionLink(tx.id, index)) + for index, cond in enumerate(tx.conditions)] + conditions = [Condition(Ed25519Fulfillment(public_key=user3_pub), + [user3_pub]), + Condition(Ed25519Fulfillment(public_key=user3_pub), + [user3_pub])] + transfer_tx = Transaction('TRANSFER', tx.asset, fulfillments, conditions) + transfer_tx = transfer_tx.sign([user_priv]) + + assert transfer_tx.fulfillments_valid(tx.conditions) is True + + +def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, + cond_uri, + utx, + user2_pub, + user_priv): + from bigchaindb.common.transaction import Condition + from cryptoconditions import Ed25519Fulfillment + + invalid_cond = Condition(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid']) + assert transfer_tx.fulfillments_valid([invalid_cond]) is False + invalid_cond = utx.conditions[0] + invalid_cond.owners_after = 'invalid' + assert transfer_tx.fulfillments_valid([invalid_cond]) is True + + with raises(TypeError): + assert transfer_tx.fulfillments_valid(None) is False + with raises(AttributeError): + transfer_tx.fulfillments_valid('not a list') + with raises(ValueError): + transfer_tx.fulfillments_valid([]) + 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, + data_id): + from bigchaindb.common.transaction import Transaction, Asset + + expected = { + 'transaction': { + 'conditions': [user_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], [user_pub], data, asset).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_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 = 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 + + expected = { + 'transaction': { + 'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)], + 'metadata': { + 'data': { + 'message': 'hello' + } + }, + 'fulfillments': [ + { + 'owners_before': [ + user_pub, + ], + 'fid': 0, + 'fulfillment': None, + 'input': None + }, + { + 'owners_before': [ + user2_pub, + ], + 'fid': 1, + 'fulfillment': None, + 'input': None + } + ], + 'operation': 'CREATE', + }, + 'version': 1 + } + tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], + {'message': 'hello'}).to_dict() + tx.pop('id') + tx['transaction']['metadata'].pop('id') + tx['transaction'].pop('timestamp') + + 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 + + tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], + {'message': 'hello'}) + tx = tx.sign([user_priv, user2_priv]) + assert tx.fulfillments_valid() is True + + +def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, + user_user2_threshold_cond, + user_user2_threshold_ffill, data, + data_id): + from bigchaindb.common.transaction import Transaction, Asset + + expected = { + 'transaction': { + 'conditions': [user_user2_threshold_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], [user_pub, user2_pub], data, asset) + tx_dict = tx.to_dict() + tx_dict.pop('id') + tx_dict['transaction']['metadata'].pop('id') + tx_dict['transaction'].pop('timestamp') + tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None + + assert tx_dict == expected + + +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 = 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(): + 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) + with raises(ValueError): + Transaction.create([], [], secret='wow, much secret') + + +def test_conditions_to_inputs(tx): + ffills = tx.to_inputs([0]) + assert len(ffills) == 1 + ffill = ffills.pop() + assert ffill.fulfillment == tx.conditions[0].fulfillment + assert ffill.owners_before == tx.conditions[0].owners_after + assert ffill.tx_input.txid == tx.id + assert ffill.tx_input.cid == 0 + + +def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, + user2_cond, user_priv, data_id): + from copy import deepcopy + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.util import serialize + + expected = { + 'transaction': { + 'conditions': [user2_cond.to_dict(0)], + 'metadata': None, + 'asset': { + 'id': data_id, + }, + 'fulfillments': [ + { + 'owners_before': [ + user_pub + ], + 'fid': 0, + 'fulfillment': None, + 'input': { + 'txid': tx.id, + 'cid': 0 + } + } + ], + 'operation': 'TRANSFER', + }, + 'version': 1 + } + inputs = tx.to_inputs([0]) + asset = Asset(None, data_id) + transfer_tx = Transaction.transfer(inputs, [user2_pub], asset=asset) + transfer_tx = transfer_tx.sign([user_priv]) + transfer_tx = transfer_tx.to_dict() + transfer_tx_body = transfer_tx['transaction'] + + expected_input = deepcopy(inputs[0]) + expected['id'] = transfer_tx['id'] + expected['transaction']['timestamp'] = transfer_tx_body['timestamp'] + expected_input.fulfillment.sign(serialize(expected).encode(), + SigningKey(user_priv)) + expected_ffill = expected_input.fulfillment.serialize_uri() + transfer_ffill = transfer_tx_body['fulfillments'][0]['fulfillment'] + + assert transfer_ffill == expected_ffill + + transfer_tx = Transaction.from_dict(transfer_tx) + 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 + + 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]) + + expected = { + 'transaction': { + 'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)], + 'metadata': None, + 'fulfillments': [ + { + 'owners_before': [ + user_pub + ], + 'fid': 0, + 'fulfillment': None, + 'input': { + 'txid': tx1.id, + 'cid': 0 + } + }, { + 'owners_before': [ + user2_pub + ], + 'fid': 1, + 'fulfillment': None, + 'input': { + 'txid': tx2.id, + 'cid': 0 + } + } + ], + 'operation': 'TRANSFER', + }, + '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 = 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 + + 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') + + assert expected == transfer_tx + + +def test_create_transfer_with_invalid_parameters(): + from bigchaindb.common.transaction import Transaction, Asset + + with raises(TypeError): + Transaction.transfer({}, [], Asset()) + with raises(ValueError): + Transaction.transfer([], [], Asset()) + with raises(TypeError): + Transaction.transfer(['fulfillment'], {}, Asset()) + with raises(ValueError): + Transaction.transfer(['fulfillment'], [], Asset()) + + +def test_cant_add_empty_condition(): + from bigchaindb.common.transaction import Transaction + 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) + with raises(TypeError): + tx.add_fulfillment(None) diff --git a/tests/db/conftest.py b/tests/db/conftest.py index ec4afc9b..f55a4c34 100644 --- a/tests/db/conftest.py +++ b/tests/db/conftest.py @@ -11,7 +11,7 @@ import rethinkdb as r from bigchaindb import Bigchain from bigchaindb.db import get_conn -from bigchaindb_common import crypto +from bigchaindb.common import crypto USER2_SK, USER2_VK = crypto.generate_key_pair() @@ -107,7 +107,7 @@ def cleanup_tables(request, node_config): @pytest.fixture def inputs(user_vk): from bigchaindb.models import Transaction - from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError + from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError # 1. create the genesis block b = Bigchain() try: @@ -144,7 +144,7 @@ def user2_vk(): @pytest.fixture def inputs_shared(user_vk, user2_vk): from bigchaindb.models import Transaction - from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError + from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError # 1. create the genesis block b = Bigchain() try: diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 48a87fdc..f0a88c44 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -30,9 +30,9 @@ def dummy_block(): class TestBigchainApi(object): def test_get_last_voted_block_cyclic_blockchain(self, b, monkeypatch): - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.exceptions import CyclicBlockchainError - from bigchaindb_common.util import serialize + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.exceptions import CyclicBlockchainError + from bigchaindb.common.util import serialize from bigchaindb.models import Transaction b.create_genesis_block() @@ -55,7 +55,7 @@ class TestBigchainApi(object): def test_try_voting_while_constructing_cyclic_blockchain(self, b, monkeypatch): - from bigchaindb_common.exceptions import CyclicBlockchainError + from bigchaindb.common.exceptions import CyclicBlockchainError from bigchaindb.models import Transaction b.create_genesis_block() @@ -94,7 +94,7 @@ class TestBigchainApi(object): assert not matches def test_get_spent_with_double_spend(self, b, monkeypatch): - from bigchaindb_common.exceptions import DoubleSpend + from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.models import Transaction b.create_genesis_block() @@ -128,7 +128,7 @@ class TestBigchainApi(object): b.get_spent(tx.id, 0) def test_get_block_status_for_tx_with_double_spend(self, b, monkeypatch): - from bigchaindb_common.exceptions import DoubleSpend + from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.models import Transaction b.create_genesis_block() @@ -277,7 +277,7 @@ class TestBigchainApi(object): def test_create_genesis_block_fails_if_table_not_empty(self, b): import rethinkdb as r - from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError + from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError from bigchaindb.util import is_genesis_block from bigchaindb.db.utils import get_conn @@ -333,7 +333,7 @@ class TestBigchainApi(object): assert prev_block_id == last_block['id'] def test_create_empty_block(self, b): - from bigchaindb_common.exceptions import OperationError + from bigchaindb.common.exceptions import OperationError with pytest.raises(OperationError) as excinfo: b.create_block([]) @@ -433,7 +433,7 @@ class TestBigchainApi(object): def test_more_votes_than_voters(self, b): import rethinkdb as r - from bigchaindb_common.exceptions import MultipleVotesError + from bigchaindb.common.exceptions import MultipleVotesError from bigchaindb.db.utils import get_conn b.create_genesis_block() @@ -453,7 +453,7 @@ class TestBigchainApi(object): def test_multiple_votes_single_node(self, b): import rethinkdb as r - from bigchaindb_common.exceptions import MultipleVotesError + from bigchaindb.common.exceptions import MultipleVotesError from bigchaindb.db.utils import get_conn genesis = b.create_genesis_block() @@ -475,7 +475,7 @@ class TestBigchainApi(object): def test_improper_vote_error(selfs, b): import rethinkdb as r - from bigchaindb_common.exceptions import ImproperVoteError + from bigchaindb.common.exceptions import ImproperVoteError from bigchaindb.db.utils import get_conn b.create_genesis_block() @@ -512,7 +512,7 @@ class TestBigchainApi(object): @pytest.mark.usefixtures('inputs') def test_assign_transaction_multiple_nodes(self, b, user_vk, user_sk): import rethinkdb as r - from bigchaindb_common.crypto import generate_key_pair + from bigchaindb.common.crypto import generate_key_pair from bigchaindb.models import Transaction from bigchaindb.db.utils import get_conn @@ -539,8 +539,8 @@ class TestBigchainApi(object): @pytest.mark.usefixtures('inputs') def test_non_create_input_not_found(self, b, user_vk): from cryptoconditions import Ed25519Fulfillment - from bigchaindb_common.exceptions import TransactionDoesNotExist - from bigchaindb_common.transaction import (Fulfillment, Asset, + from bigchaindb.common.exceptions import TransactionDoesNotExist + from bigchaindb.common.transaction import (Fulfillment, Asset, TransactionLink) from bigchaindb.models import Transaction from bigchaindb import Bigchain @@ -557,7 +557,7 @@ class TestBigchainApi(object): class TestTransactionValidation(object): def test_create_operation_with_inputs(self, b, user_vk, create_tx): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink # Manipulate fulfillment so that it has a `tx_input` defined even # though it shouldn't have one @@ -575,8 +575,8 @@ class TestTransactionValidation(object): assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs' def test_non_create_input_not_found(self, b, user_vk, signed_transfer_tx): - from bigchaindb_common.exceptions import TransactionDoesNotExist - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.exceptions import TransactionDoesNotExist + from bigchaindb.common.transaction import TransactionLink signed_transfer_tx.fulfillments[0].tx_input = TransactionLink('c', 0) with pytest.raises(TransactionDoesNotExist): @@ -584,8 +584,8 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_non_create_valid_input_wrong_owner(self, b, user_vk): - from bigchaindb_common.crypto import generate_key_pair - from bigchaindb_common.exceptions import InvalidSignature + from bigchaindb.common.crypto import generate_key_pair + from bigchaindb.common.exceptions import InvalidSignature from bigchaindb.models import Transaction input_tx = b.get_owned_ids(user_vk).pop() @@ -602,7 +602,7 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_non_create_double_spend(self, b, signed_create_tx, signed_transfer_tx): - from bigchaindb_common.exceptions import DoubleSpend + from bigchaindb.common.exceptions import DoubleSpend block1 = b.create_block([signed_create_tx]) b.write_block(block1) @@ -652,7 +652,7 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_fulfillment_not_in_valid_block(self, b, user_vk, user_sk): from bigchaindb.models import Transaction - from bigchaindb_common.exceptions import FulfillmentNotInValidBlock + from bigchaindb.common.exceptions import FulfillmentNotInValidBlock input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) @@ -681,9 +681,9 @@ class TestBlockValidation(object): @pytest.mark.skipif(reason='Separated tx validation from block creation.') @pytest.mark.usefixtures('inputs') def test_invalid_transactions_in_block(self, b, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.exceptions import TransactionOwnerError - from bigchaindb_common.util import gen_timestamp + from bigchaindb.common import crypto + from bigchaindb.common.exceptions import TransactionOwnerError + from bigchaindb.common.util import gen_timestamp from bigchaindb import util @@ -722,8 +722,8 @@ class TestBlockValidation(object): assert excinfo.value.args[0] == 'owner_before `a` does not own the input `{}`'.format(valid_input) def test_invalid_signature(self, b): - from bigchaindb_common.exceptions import InvalidSignature - from bigchaindb_common import crypto + from bigchaindb.common.exceptions import InvalidSignature + from bigchaindb.common import crypto # create a valid block block = dummy_block() @@ -736,8 +736,8 @@ class TestBlockValidation(object): b.validate_block(block) def test_invalid_node_pubkey(self, b): - from bigchaindb_common.exceptions import OperationError - from bigchaindb_common import crypto + from bigchaindb.common.exceptions import OperationError + from bigchaindb.common import crypto # blocks can only be created by a federation node # create a valid block @@ -761,7 +761,7 @@ class TestBlockValidation(object): class TestMultipleInputs(object): def test_transfer_single_owner_single_input(self, b, inputs, user_vk, user_sk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -781,7 +781,7 @@ class TestMultipleInputs(object): '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.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -804,7 +804,7 @@ class TestMultipleInputs(object): def test_transfer_single_owners_single_input_from_multiple_outputs(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -843,7 +843,7 @@ class TestMultipleInputs(object): user_sk, user_vk, inputs): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -866,7 +866,7 @@ class TestMultipleInputs(object): def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -897,7 +897,7 @@ class TestMultipleInputs(object): def test_multiple_owners_before_single_owner_after_single_input(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -930,7 +930,7 @@ class TestMultipleInputs(object): @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.common import crypto from bigchaindb.models import Transaction # create a new users @@ -951,7 +951,7 @@ class TestMultipleInputs(object): def test_multiple_owners_before_multiple_owners_after_single_input(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -985,7 +985,7 @@ class TestMultipleInputs(object): 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.common import crypto from bigchaindb.models import Transaction # create a new users @@ -1004,8 +1004,8 @@ class TestMultipleInputs(object): 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 + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1033,8 +1033,8 @@ class TestMultipleInputs(object): def test_get_owned_ids_single_tx_single_output_invalid_block(self, b, user_sk, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction genesis = b.create_genesis_block() @@ -1078,8 +1078,8 @@ class TestMultipleInputs(object): 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 import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1115,8 +1115,8 @@ class TestMultipleInputs(object): TransactionLink(tx.id, 1)] def test_get_owned_ids_multiple_owners(self, b, user_sk, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1145,7 +1145,7 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [] def test_get_spent_single_tx_single_output(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1173,7 +1173,7 @@ class TestMultipleInputs(object): assert spent_inputs_user1 == tx def test_get_spent_single_tx_single_output_invalid_block(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction genesis = b.create_genesis_block() @@ -1219,7 +1219,7 @@ class TestMultipleInputs(object): '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.common import crypto from bigchaindb.models import Transaction # create a new users @@ -1259,7 +1259,7 @@ class TestMultipleInputs(object): def test_get_spent_multiple_owners(self, b, user_sk, user_vk): import random - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() diff --git a/tests/db/test_utils.py b/tests/db/test_utils.py index 56f09ac5..75d14c02 100644 --- a/tests/db/test_utils.py +++ b/tests/db/test_utils.py @@ -1,6 +1,6 @@ import builtins -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions import pytest import rethinkdb as r diff --git a/tests/doc/run_doc_python_server_api_examples.py b/tests/doc/run_doc_python_server_api_examples.py index b1f0ed9b..a7bf89d5 100644 --- a/tests/doc/run_doc_python_server_api_examples.py +++ b/tests/doc/run_doc_python_server_api_examples.py @@ -2,7 +2,7 @@ import json from time import sleep import cryptoconditions as cc -from bigchaindb_common.util import gen_timestamp +from bigchaindb.common.util import gen_timestamp from bigchaindb import Bigchain, util, crypto, exceptions diff --git a/tests/pipelines/test_election.py b/tests/pipelines/test_election.py index 7887b4c9..669a75cb 100644 --- a/tests/pipelines/test_election.py +++ b/tests/pipelines/test_election.py @@ -1,7 +1,7 @@ import time from unittest.mock import patch -from bigchaindb_common import crypto +from bigchaindb.common import crypto import rethinkdb as r from multipipes import Pipe, Pipeline diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index 52ac117e..5bd0eb52 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -19,8 +19,8 @@ def dummy_block(b): def test_vote_creation_valid(b): - from bigchaindb_common import crypto - from bigchaindb_common.util import serialize + from bigchaindb.common import crypto + from bigchaindb.common.util import serialize # create valid block block = dummy_block(b) @@ -38,8 +38,8 @@ def test_vote_creation_valid(b): def test_vote_creation_invalid(b): - from bigchaindb_common import crypto - from bigchaindb_common.util import serialize + from bigchaindb.common import crypto + from bigchaindb.common.util import serialize # create valid block block = dummy_block(b) @@ -154,7 +154,7 @@ def test_vote_accumulates_transactions(b): def test_valid_block_voting_sequential(b, monkeypatch): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote monkeypatch.setattr('time.time', lambda: 1) @@ -182,7 +182,7 @@ def test_valid_block_voting_sequential(b, monkeypatch): def test_valid_block_voting_multiprocessing(b, monkeypatch): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote inpipe = Pipe() @@ -216,7 +216,7 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch): def test_valid_block_voting_with_create_transaction(b, monkeypatch): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -257,7 +257,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch): def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -325,7 +325,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): def test_unsigned_tx_in_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -362,7 +362,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_vk): def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -401,7 +401,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_vk): def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -440,7 +440,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_vk): def test_invalid_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote inpipe = Pipe() diff --git a/tests/test_commands.py b/tests/test_commands.py index 6cb19d48..95fc4179 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -22,7 +22,7 @@ def mock_write_config(monkeypatch): @pytest.fixture def mock_db_init_with_existing_db(monkeypatch): from bigchaindb import db - from bigchaindb_common.exceptions import DatabaseAlreadyExists + from bigchaindb.common.exceptions import DatabaseAlreadyExists def mockreturn(): raise DatabaseAlreadyExists @@ -48,7 +48,7 @@ def mock_rethink_db_drop(monkeypatch): @pytest.fixture def mock_generate_key_pair(monkeypatch): - monkeypatch.setattr('bigchaindb_common.crypto.generate_key_pair', lambda: ('privkey', 'pubkey')) + monkeypatch.setattr('bigchaindb.common.crypto.generate_key_pair', lambda: ('privkey', 'pubkey')) @pytest.fixture @@ -283,14 +283,14 @@ def test_start_rethinkdb_returns_a_process_when_successful(mock_popen): @patch('subprocess.Popen') def test_start_rethinkdb_exits_when_cannot_start(mock_popen): - from bigchaindb_common import exceptions + from bigchaindb.common import exceptions from bigchaindb.commands import utils mock_popen.return_value = Mock(stdout=['Nopety nope']) with pytest.raises(exceptions.StartupError): utils.start_rethinkdb() -@patch('bigchaindb_common.crypto.generate_key_pair', +@patch('bigchaindb.common.crypto.generate_key_pair', return_value=('private_key', 'public_key')) def test_allow_temp_keypair_generates_one_on_the_fly(mock_gen_keypair, mock_processes_start, @@ -307,7 +307,7 @@ def test_allow_temp_keypair_generates_one_on_the_fly(mock_gen_keypair, assert bigchaindb.config['keypair']['public'] == 'public_key' -@patch('bigchaindb_common.crypto.generate_key_pair', +@patch('bigchaindb.common.crypto.generate_key_pair', return_value=('private_key', 'public_key')) def test_allow_temp_keypair_doesnt_override_if_keypair_found(mock_gen_keypair, mock_processes_start, diff --git a/tests/test_config_utils.py b/tests/test_config_utils.py index 5ffd5b9a..ea0a0528 100644 --- a/tests/test_config_utils.py +++ b/tests/test_config_utils.py @@ -42,7 +42,7 @@ def test_bigchain_instance_is_initialized_when_conf_provided(): def test_bigchain_instance_raises_when_not_configured(monkeypatch): from bigchaindb import config_utils - from bigchaindb_common import exceptions + from bigchaindb.common import exceptions assert 'CONFIGURED' not in bigchaindb.config # We need to disable ``bigchaindb.config_utils.autoconfigure`` to avoid reading @@ -204,7 +204,7 @@ def test_file_config(): def test_invalid_file_config(): from bigchaindb.config_utils import file_config - from bigchaindb_common import exceptions + from bigchaindb.common import exceptions with patch('builtins.open', mock_open(read_data='{_INVALID_JSON_}')): with pytest.raises(exceptions.ConfigurationError): file_config() diff --git a/tests/test_models.py b/tests/test_models.py index faef5353..5033aebb 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -37,8 +37,8 @@ class TestBlockModel(object): 'not a list or None') def test_block_serialization(self, b): - from bigchaindb_common.crypto import hash_data - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import hash_data + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] @@ -61,7 +61,7 @@ class TestBlockModel(object): assert block.to_dict() == expected def test_block_invalid_serializaton(self): - from bigchaindb_common.exceptions import OperationError + from bigchaindb.common.exceptions import OperationError from bigchaindb.models import Block block = Block([]) @@ -69,8 +69,8 @@ class TestBlockModel(object): block.to_dict() def test_block_deserialization(self, b): - from bigchaindb_common.crypto import hash_data - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import hash_data + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] @@ -94,7 +94,7 @@ class TestBlockModel(object): assert expected == Block.from_dict(block_body) def test_block_invalid_id_deserialization(self, b): - from bigchaindb_common.exceptions import InvalidHash + from bigchaindb.common.exceptions import InvalidHash from bigchaindb.models import Block block = { @@ -108,9 +108,9 @@ class TestBlockModel(object): Block.from_dict(block) def test_block_invalid_signature_deserialization(self, b): - from bigchaindb_common.crypto import hash_data - from bigchaindb_common.exceptions import InvalidSignature - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import hash_data + from bigchaindb.common.exceptions import InvalidSignature + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] @@ -142,8 +142,8 @@ class TestBlockModel(object): assert Block(transactions) == Block(transactions) def test_sign_block(self, b): - from bigchaindb_common.crypto import SigningKey, VerifyingKey - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import SigningKey, VerifyingKey + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index b941011a..00e40a37 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -1,7 +1,7 @@ import json import pytest -from bigchaindb_common import crypto +from bigchaindb.common import crypto TX_ENDPOINT = '/api/v1/transactions/'