diff --git a/bigchaindb/backend/rethinkdb/query.py b/bigchaindb/backend/rethinkdb/query.py index ce999ebc..9439022f 100644 --- a/bigchaindb/backend/rethinkdb/query.py +++ b/bigchaindb/backend/rethinkdb/query.py @@ -105,13 +105,13 @@ def _get_asset_create_tx_query(asset_id): @register_query(RethinkDBConnection) -def get_spent(connection, transaction_id, condition_id): +def get_spent(connection, transaction_id, output): # TODO: use index! return connection.run( r.table('bigchain', read_mode=READ_MODE) .concat_map(lambda doc: doc['block']['transactions']) - .filter(lambda transaction: transaction['fulfillments'].contains( - lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id}))) + .filter(lambda transaction: transaction['inputs'].contains( + lambda input: input['fulfills'] == {'txid': transaction_id, 'output': output}))) @register_query(RethinkDBConnection) @@ -120,8 +120,8 @@ def get_owned_ids(connection, owner): return connection.run( r.table('bigchain', read_mode=READ_MODE) .concat_map(lambda doc: doc['block']['transactions']) - .filter(lambda tx: tx['conditions'].contains( - lambda c: c['owners_after'].contains(owner)))) + .filter(lambda tx: tx['outputs'].contains( + lambda c: c['public_keys'].contains(owner)))) @register_query(RethinkDBConnection) diff --git a/bigchaindb/common/exceptions.py b/bigchaindb/common/exceptions.py index f7d617f0..c4ccd083 100644 --- a/bigchaindb/common/exceptions.py +++ b/bigchaindb/common/exceptions.py @@ -79,7 +79,7 @@ class CyclicBlockchainError(Exception): class TransactionNotInValidBlock(Exception): """Raised when a transfer transaction is attempting to fulfill the - conditions of a transaction that is in an invalid or undecided block""" + outputs of a transaction that is in an invalid or undecided block""" class AssetIdMismatch(Exception): diff --git a/bigchaindb/common/schema/transaction.yaml b/bigchaindb/common/schema/transaction.yaml index 2af489f9..e856271d 100644 --- a/bigchaindb/common/schema/transaction.yaml +++ b/bigchaindb/common/schema/transaction.yaml @@ -8,8 +8,8 @@ description: | A transaction represents the creation or transfer of assets in BigchainDB. required: - id -- fulfillments -- conditions +- inputs +- outputs - operation - metadata - asset @@ -30,23 +30,23 @@ properties: Description of the asset being transacted. See: `Asset`_. - fulfillments: + inputs: type: array - title: "Fulfillments list" + title: "Transaction inputs" description: | - Array of the fulfillments (inputs) of a transaction. + Array of the inputs of a transaction. - See: Fulfillment_. + See: Input_. items: - "$ref": "#/definitions/fulfillment" - conditions: + "$ref": "#/definitions/input" + outputs: type: array description: | - Array of conditions (outputs) provided by this transaction. + Array of outputs provided by this transaction. - See: Condition_. + See: Output_. items: - "$ref": "#/definitions/condition" + "$ref": "#/definitions/output" metadata: "$ref": "#/definitions/metadata" description: | @@ -67,7 +67,7 @@ definitions: base58: pattern: "[1-9a-zA-Z^OIl]{43,44}" type: string - owners_list: + public_keys: anyOf: - type: array items: @@ -88,11 +88,10 @@ definitions: Type of the transaction: A ``CREATE`` transaction creates an asset in BigchainDB. This - transaction has outputs (conditions) but no inputs (fulfillments), - so a dummy fulfillment is used. + transaction has outputs but no inputs, so a dummy input is created. A ``TRANSFER`` transaction transfers ownership of an asset, by providing - fulfillments to conditions of earlier transactions. + an input that meets the conditions of an earlier transaction's outputs. A ``GENESIS`` transaction is a special case transaction used as the sole member of the first block in a BigchainDB ledger. @@ -120,21 +119,27 @@ definitions: - type: object additionalProperties: true - type: 'null' - condition: + output: type: object description: | - An output of a transaction. A condition describes a quantity of an asset - and what conditions must be met in order for it to be fulfilled. See also: - fulfillment_. + A transaction output. Describes the quantity of an asset and the + requirements that must be met to spend the output. + + See also: Input_. additionalProperties: false required: - - owners_after - - condition - amount + - condition + - public_keys properties: + amount: + type: integer + description: | + Integral amount of the asset represented by this output. + In the case of a non divisible asset, this will always be 1. condition: description: | - Body of the condition. Has the properties: + Describes the condition that needs to be met to spend the output. Has the properties: - **details**: Details of the condition. - **uri**: Condition encoded as an ASCII string. @@ -150,29 +155,26 @@ definitions: uri: type: string pattern: "^cc:([1-9a-f][0-9a-f]{0,3}|0):[1-9a-f][0-9a-f]{0,15}:[a-zA-Z0-9_-]{0,86}:([1-9][0-9]{0,17}|0)$" - owners_after: - "$ref": "#/definitions/owners_list" + public_keys: + "$ref": "#/definitions/public_keys" description: | - List of public keys associated with asset ownership at the time - of the transaction. + List of public keys associated with the conditions on an output. amount: type: integer description: | Integral amount of the asset represented by this condition. - fulfillment: + input: type: "object" description: - A fulfillment is an input to a transaction, named as such because it - fulfills a condition of a previous transaction. In the case of a - ``CREATE`` transaction, a fulfillment may provide no ``input``. + An input spends a previous output, by providing one or more fulfillments + that fulfill the conditions of the previous output. additionalProperties: false required: - owners_before - - input - fulfillment properties: owners_before: - "$ref": "#/definitions/owners_list" + "$ref": "#/definitions/public_keys" description: | List of public keys of the previous owners of the asset. fulfillment: @@ -193,22 +195,29 @@ definitions: type_id: type: integer description: | - Fulfillment of a condition_, or put a different way, this is a - payload that satisfies a condition in order to spend the associated - asset. + Fulfillment of an `Output.condition`_, or, put a different way, a payload + that satisfies the condition of a previous output to prove that the + creator(s) of this transaction have control over the listed asset. - type: string pattern: "^cf:([1-9a-f][0-9a-f]{0,3}|0):[a-zA-Z0-9_-]*$" - input: + fulfills: anyOf: - type: 'object' description: | - Reference to a condition of a previous transaction + Reference to the output that is being spent. additionalProperties: false + required: + - output + - txid properties: - cid: + output: "$ref": "#/definitions/offset" + description: | + Index of the output containing the condition being fulfilled txid: "$ref": "#/definitions/sha3_hexdigest" + description: | + Transaction ID containing the output to spend - type: 'null' metadata: anyOf: diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index 245b89c7..7810e688 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -1,8 +1,8 @@ from copy import deepcopy from functools import reduce -from cryptoconditions import (Fulfillment as CCFulfillment, - ThresholdSha256Fulfillment, Ed25519Fulfillment) +from cryptoconditions import (Fulfillment, ThresholdSha256Fulfillment, + Ed25519Fulfillment) from cryptoconditions.exceptions import ParsingError from bigchaindb.common.crypto import PrivateKey, hash_data @@ -12,39 +12,40 @@ from bigchaindb.common.exceptions import (KeypairMismatchException, from bigchaindb.common.util import serialize, gen_timestamp -class Fulfillment(object): - """A Fulfillment is used to spend assets locked by a Condition. +class Input(object): + """A Input is used to spend assets locked by an Output. + + Wraps around a Crypto-condition Fulfillment. 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`, + fulfills (: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. + def __init__(self, fulfillment, owners_before, fulfills=None): + """Create an instance of an :class:`~.Input`. 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. + fulfills (:class:`~bigchaindb.common.transaction. TransactionLink`, optional): A link representing the input of a `TRANSFER` Transaction. """ - if tx_input is not None and not isinstance(tx_input, TransactionLink): - raise TypeError('`tx_input` must be a TransactionLink instance') - + if fulfills is not None and not isinstance(fulfills, TransactionLink): + raise TypeError('`fulfills` must be a TransactionLink instance') if not isinstance(owners_before, list): raise TypeError('`owners_after` must be a list instance') self.fulfillment = fulfillment - self.tx_input = tx_input + self.fulfills = fulfills self.owners_before = owners_before def __eq__(self, other): @@ -55,17 +56,17 @@ class Fulfillment(object): """Transforms the object to a Python dictionary. Note: - If a Fulfillment hasn't been signed yet, this method returns a + If an Input hasn't been signed yet, this method returns a dictionary representation. Returns: - dict: The Fulfillment as an alternative serialization format. + dict: The Input 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 + # `self.inputs` 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. @@ -75,85 +76,85 @@ class Fulfillment(object): fulfillment = self.fulfillment.to_dict() try: - # NOTE: `self.tx_input` can be `None` and that's fine - tx_input = self.tx_input.to_dict() + # NOTE: `self.fulfills` can be `None` and that's fine + fulfills = self.fulfills.to_dict() except AttributeError: - tx_input = None + fulfills = None - ffill = { + input_ = { 'owners_before': self.owners_before, - 'input': tx_input, + 'fulfills': fulfills, 'fulfillment': fulfillment, } - return ffill + return input_ @classmethod - def generate(cls, owners_before): + def generate(cls, public_keys): # TODO: write docstring # The amount here does not really matter. It is only use on the - # condition data model but here we only care about the fulfillment - condition = Condition.generate(owners_before, 1) - return cls(condition.fulfillment, condition.owners_after) + # output data model but here we only care about the fulfillment + output = Output.generate(public_keys, 1) + return cls(output.fulfillment, public_keys) @classmethod - def from_dict(cls, ffill): - """Transforms a Python dictionary to a Fulfillment object. + def from_dict(cls, data): + """Transforms a Python dictionary to an Input object. Note: Optionally, this method can also serialize a Cryptoconditions- Fulfillment that is not yet signed. Args: - ffill (dict): The Fulfillment to be transformed. + data (dict): The Input to be transformed. Returns: - :class:`~bigchaindb.common.transaction.Fulfillment` + :class:`~bigchaindb.common.transaction.Input` Raises: - InvalidSignature: If a Fulfillment's URI couldn't be parsed. + InvalidSignature: If an Input's URI couldn't be parsed. """ try: - fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) + fulfillment = Fulfillment.from_uri(data['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_) + # `Input.to_dict` + fulfillment = Fulfillment.from_dict(data['fulfillment']) + fulfills = TransactionLink.from_dict(data['fulfills']) + return cls(fulfillment, data['owners_before'], fulfills) class TransactionLink(object): - """An object for unidirectional linking to a Transaction's Condition. + """An object for unidirectional linking to a Transaction's Output. Attributes: txid (str, optional): A Transaction to link to. - cid (int, optional): A Condition's index in a Transaction with id + output (int, optional): An output'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. + def __init__(self, txid=None, output=None): + """Create an instance of a :class:`~.TransactionLink`. 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//`. + `//transaction/outputs//`. Args: txid (str, optional): A Transaction to link to. - cid (int, optional): A Condition's index in a Transaction with + output (int, optional): An Outputs's index in a Transaction with id `txid`. """ self.txid = txid - self.cid = cid + self.output = output def __bool__(self): - return self.txid is not None and self.cid is not None + return self.txid is not None and self.output is not None def __eq__(self, other): # TODO: If `other !== TransactionLink` return `False` @@ -170,7 +171,7 @@ class TransactionLink(object): :class:`~bigchaindb.common.transaction.TransactionLink` """ try: - return cls(link['txid'], link['cid']) + return cls(link['txid'], link['output']) except TypeError: return cls() @@ -180,47 +181,49 @@ class TransactionLink(object): Returns: (dict|None): The link as an alternative serialization format. """ - if self.txid is None and self.cid is None: + if self.txid is None and self.output is None: return None else: return { 'txid': self.txid, - 'cid': self.cid, + 'output': self.output, } def to_uri(self, path=''): - if self.txid is None and self.cid is None: + if self.txid is None and self.output is None: return None - return '{}/transactions/{}/conditions/{}'.format(path, self.txid, - self.cid) + return '{}/transactions/{}/outputs/{}'.format(path, self.txid, + self.output) -class Condition(object): - """A Condition is used to lock an asset. +class Output(object): + """An Output is used to lock an asset. + + Wraps around a Crypto-condition Condition. Attributes: fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment to extract a Condition from. - owners_after (:obj:`list` of :obj:`str`, optional): A list of + public_keys (: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. + def __init__(self, fulfillment, public_keys=None, amount=1): + """Create an instance of a :class:`~.Output`. Args: fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment to extract a Condition from. - owners_after (:obj:`list` of :obj:`str`, optional): A list of + public_keys (: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. + Output. Raises: - TypeError: if `owners_after` is not instance of `list`. + TypeError: if `public_keys` is not instance of `list`. """ - if not isinstance(owners_after, list) and owners_after is not None: - raise TypeError('`owners_after` must be a list instance or None') + if not isinstance(public_keys, list) and public_keys is not None: + raise TypeError('`public_keys` must be a list instance or None') if not isinstance(amount, int): raise TypeError('`amount` must be an int') if amount < 1: @@ -228,7 +231,7 @@ class Condition(object): self.fulfillment = fulfillment self.amount = amount - self.owners_after = owners_after + self.public_keys = public_keys def __eq__(self, other): # TODO: If `other !== Condition` return `False` @@ -238,11 +241,11 @@ class Condition(object): """Transforms the object to a Python dictionary. Note: - A dictionary serialization of the Fulfillment the Condition was + A dictionary serialization of the Input the Output was derived from is always provided. Returns: - dict: The Condition as an alternative serialization format. + dict: The Output as an alternative serialization format. """ # TODO FOR CC: It must be able to recognize a hashlock condition # and fulfillment! @@ -257,16 +260,16 @@ class Condition(object): except AttributeError: condition['uri'] = self.fulfillment - cond = { - 'owners_after': self.owners_after, + output = { + 'public_keys': self.public_keys, 'condition': condition, 'amount': self.amount } - return cond + return output @classmethod - def generate(cls, owners_after, amount): - """Generates a Condition from a specifically formed tuple or list. + def generate(cls, public_keys, amount): + """Generates a Output from a specifically formed tuple or list. Note: If a ThresholdCondition has to be generated where the threshold @@ -276,90 +279,89 @@ class Condition(object): [(address|condition)*, [(address|condition)*, ...], ...] Args: - owners_after (:obj:`list` of :obj:`str`): The public key of + public_keys (:obj:`list` of :obj:`str`): The public key of the users that should be able to fulfill the Condition that is being created. - amount (:obj:`int`): The amount locked by the condition. + amount (:obj:`int`): The amount locked by the Output. Returns: - A Condition that can be used in a Transaction. + An Output that can be used in a Transaction. Raises: - TypeError: If `owners_after` is not an instance of `list`. - ValueError: If `owners_after` is an empty list. + TypeError: If `public_keys` is not an instance of `list`. + ValueError: If `public_keys` is an empty list. """ - threshold = len(owners_after) + threshold = len(public_keys) if not isinstance(amount, int): raise TypeError('`amount` must be a int') if amount < 1: raise AmountError('`amount` needs to be greater than zero') - if not isinstance(owners_after, list): - raise TypeError('`owners_after` must be an instance of list') - if len(owners_after) == 0: - raise ValueError('`owners_after` needs to contain at least one' + if not isinstance(public_keys, list): + raise TypeError('`public_keys` must be an instance of list') + if len(public_keys) == 0: + raise ValueError('`public_keys` needs to contain at least one' 'owner') - elif len(owners_after) == 1 and not isinstance(owners_after[0], list): + elif len(public_keys) == 1 and not isinstance(public_keys[0], list): try: - ffill = Ed25519Fulfillment(public_key=owners_after[0]) + ffill = Ed25519Fulfillment(public_key=public_keys[0]) except TypeError: - ffill = owners_after[0] - return cls(ffill, owners_after, amount=amount) + ffill = public_keys[0] + return cls(ffill, public_keys, amount=amount) else: initial_cond = ThresholdSha256Fulfillment(threshold=threshold) - threshold_cond = reduce(cls._gen_condition, owners_after, + threshold_cond = reduce(cls._gen_condition, public_keys, initial_cond) - return cls(threshold_cond, owners_after, amount=amount) + return cls(threshold_cond, public_keys, amount=amount) @classmethod - def _gen_condition(cls, initial, current): + def _gen_condition(cls, initial, new_public_keys): """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`. + :meth:`~.Output.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. + new_public_keys (:obj:`list` of :obj:`str`|str): A list of new + owners or a single new owner. Returns: :class:`cryptoconditions.ThresholdSha256Fulfillment`: """ - owners_after = current try: - threshold = len(owners_after) + threshold = len(new_public_keys) except TypeError: threshold = None - if isinstance(owners_after, list) and len(owners_after) > 1: + if isinstance(new_public_keys, list) and len(new_public_keys) > 1: ffill = ThresholdSha256Fulfillment(threshold=threshold) - reduce(cls._gen_condition, owners_after, ffill) - elif isinstance(owners_after, list) and len(owners_after) <= 1: + reduce(cls._gen_condition, new_public_keys, ffill) + elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1: raise ValueError('Sublist cannot contain single owner') else: try: - owners_after = owners_after.pop() + new_public_keys = new_public_keys.pop() except AttributeError: pass try: - ffill = Ed25519Fulfillment(public_key=owners_after) + ffill = Ed25519Fulfillment(public_key=new_public_keys) 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 + # Cryptoconditions. In the case of casting + # `new_public_keys` to a Ed25519Fulfillment with the + # result of a `TypeError`, we're assuming that + # `new_public_keys` is a Cryptocondition then. + ffill = new_public_keys initial.add_subfulfillment(ffill) return initial @classmethod - def from_dict(cls, cond): - """Transforms a Python dictionary to a Condition object. + def from_dict(cls, data): + """Transforms a Python dictionary to an Output object. Note: To pass a serialization cycle multiple times, a @@ -368,33 +370,33 @@ class Condition(object): anymore. Args: - cond (dict): The Condition to be transformed. + data (dict): The dict to be transformed. Returns: - :class:`~bigchaindb.common.transaction.Condition` + :class:`~bigchaindb.common.transaction.Output` """ try: - fulfillment = CCFulfillment.from_dict(cond['condition']['details']) + fulfillment = Fulfillment.from_dict(data['condition']['details']) except KeyError: # NOTE: Hashlock condition case - fulfillment = cond['condition']['uri'] - return cls(fulfillment, cond['owners_after'], cond['amount']) + fulfillment = data['condition']['uri'] + return cls(fulfillment, data['public_keys'], data['amount']) class Transaction(object): """A Transaction is used to create and transfer assets. Note: - For adding Fulfillments and Conditions, this class provides methods + For adding Inputs and Outputs, 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 + inputs (:obj:`list` of :class:`~bigchaindb.common. + transaction.Input`, optional): Define the assets to spend. - conditions (:obj:`list` of :class:`~bigchaindb.common. - transaction.Condition`, optional): Define the assets to lock. + outputs (:obj:`list` of :class:`~bigchaindb.common. + transaction.Output`, optional): Define the assets to lock. asset (dict): Asset payload for this Transaction. ``CREATE`` and ``GENESIS`` Transactions require a dict with a ``data`` property while ``TRANSFER`` Transactions require a dict with a @@ -409,7 +411,7 @@ class Transaction(object): ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS) VERSION = 1 - def __init__(self, operation, asset, fulfillments=None, conditions=None, + def __init__(self, operation, asset, inputs=None, outputs=None, metadata=None, version=None): """The constructor allows to create a customizable Transaction. @@ -420,11 +422,10 @@ class Transaction(object): Args: operation (str): Defines the operation of the Transaction. asset (dict): Asset payload for this 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 + inputs (:obj:`list` of :class:`~bigchaindb.common. + transaction.Input`, optional): Define the assets to + outputs (:obj:`list` of :class:`~bigchaindb.common. + transaction.Output`, optional): Define the assets to lock. metadata (dict): Metadata to be stored along with the Transaction. @@ -447,11 +448,11 @@ class Transaction(object): raise TypeError(('`asset` must be a dict holding an `id` property ' "for 'TRANSFER' Transactions".format(operation))) - if conditions and not isinstance(conditions, list): - raise TypeError('`conditions` must be a list instance or None') + if outputs and not isinstance(outputs, list): + raise TypeError('`outputs` must be a list instance or None') - if fulfillments and not isinstance(fulfillments, list): - raise TypeError('`fulfillments` must be a list instance or None') + if inputs and not isinstance(inputs, list): + raise TypeError('`inputs` must be a list instance or None') if metadata is not None and not isinstance(metadata, dict): raise TypeError('`metadata` must be a dict or None') @@ -459,12 +460,12 @@ class Transaction(object): self.version = version if version is not None else self.VERSION self.operation = operation self.asset = asset - self.conditions = conditions or [] - self.fulfillments = fulfillments or [] + self.inputs = inputs or [] + self.outputs = outputs or [] self.metadata = metadata @classmethod - def create(cls, owners_before, owners_after, metadata=None, asset=None): + def create(cls, tx_signers, recipients, metadata=None, asset=None): """A simple way to generate a `CREATE` transaction. Note: @@ -478,10 +479,11 @@ class Transaction(object): - 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. + tx_signers (:obj:`list` of :obj:`str`): A list of keys that + represent the signers of the CREATE Transaction. + recipients (:obj:`list` of :obj:`str`): A list of keys that + represent the recipients of the outputs of this + Transaction. metadata (dict): The metadata to be stored along with the Transaction. asset (dict): The metadata associated with the asset that will @@ -490,52 +492,52 @@ class Transaction(object): 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') - if len(owners_before) == 0: - raise ValueError('`owners_before` list cannot be empty') - if len(owners_after) == 0: - raise ValueError('`owners_after` list cannot be empty') + if not isinstance(tx_signers, list): + raise TypeError('`tx_signers` must be a list instance') + if not isinstance(recipients, list): + raise TypeError('`recipients` must be a list instance') + if len(tx_signers) == 0: + raise ValueError('`tx_signers` list cannot be empty') + if len(recipients) == 0: + raise ValueError('`recipients` list cannot be empty') if not (asset is None or isinstance(asset, dict)): raise TypeError('`asset` must be a dict or None') - fulfillments = [] - conditions = [] + inputs = [] + outputs = [] - # generate_conditions - for owner_after in owners_after: - if not isinstance(owner_after, tuple) or len(owner_after) != 2: - raise ValueError(('Each `owner_after` in the list must be a' + # generate_outputs + for recipient in recipients: + if not isinstance(recipient, tuple) or len(recipient) != 2: + raise ValueError(('Each `recipient` in the list must be a' ' tuple of `([],' ' )`')) - pub_keys, amount = owner_after - conditions.append(Condition.generate(pub_keys, amount)) + pub_keys, amount = recipient + outputs.append(Output.generate(pub_keys, amount)) - # generate fulfillments - fulfillments.append(Fulfillment.generate(owners_before)) + # generate inputs + inputs.append(Input.generate(tx_signers)) - return cls(cls.CREATE, {'data': asset}, fulfillments, conditions, metadata) + return cls(cls.CREATE, {'data': asset}, inputs, outputs, metadata) @classmethod - def transfer(cls, inputs, owners_after, asset_id, metadata=None): + def transfer(cls, inputs, recipients, asset_id, 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 + `recipients` 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 + 1. The index of a `recipient` 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, + 2. `recipients` 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` @@ -544,11 +546,11 @@ class Transaction(object): 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. + Input`): Converted `Output`s, intended to + be used as inputs in the transfer to generate. + recipients (:obj:`list` of :obj:`str`): A list of + ([keys],amount) that represent the recipients of this + Transaction. asset_id (str): The asset ID of the asset to be transferred in this Transaction. metadata (dict): Python dictionary to be stored along with the @@ -561,24 +563,24 @@ class Transaction(object): 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') - if len(owners_after) == 0: - raise ValueError('`owners_after` list cannot be empty') + if not isinstance(recipients, list): + raise TypeError('`recipients` must be a list instance') + if len(recipients) == 0: + raise ValueError('`recipients` list cannot be empty') + + outputs = [] + for recipient in recipients: + if not isinstance(recipient, tuple) or len(recipient) != 2: + raise ValueError(('Each `recipient` in the list must be a' + ' )`')) + pub_keys, amount = recipient + outputs.append(Output.generate(pub_keys, amount)) + if not isinstance(asset_id, str): raise TypeError('`asset_id` must be a string') - conditions = [] - for owner_after in owners_after: - if not isinstance(owner_after, tuple) or len(owner_after) != 2: - raise ValueError(('Each `owner_after` in the list must be a' - ' tuple of `([],' - ' )`')) - pub_keys, amount = owner_after - conditions.append(Condition.generate(pub_keys, amount)) - inputs = deepcopy(inputs) - return cls(cls.TRANSFER, {'id': asset_id}, inputs, conditions, metadata) + return cls(cls.TRANSFER, {'id': asset_id}, inputs, outputs, metadata) def __eq__(self, other): try: @@ -587,62 +589,61 @@ class Transaction(object): return False return self.to_dict() == other - def to_inputs(self, condition_indices=None): - """Converts a Transaction's Conditions to spendable Fulfillments. + def to_inputs(self, indices=None): + """Converts a Transaction's outputs to spendable inputs. Note: - Takes the Transaction's Conditions and derives Fulfillments - from it that can then be passed into `Transaction.transfer` as + Takes the Transaction's outputs and derives inputs + from 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. + A list of integers can be passed to `indices` that + defines which outputs should be returned as inputs. + If no `indices` are passed (empty list or None) all + outputs of the Transaction are returned. Args: - condition_indices (:obj:`list` of int): Defines which - Conditions should be returned as inputs. + indices (:obj:`list` of int): Defines which + outputs should be returned as inputs. Returns: :obj:`list` of :class:`~bigchaindb.common.transaction. - Fulfillment` + Input` """ - # NOTE: If no condition indices are passed, we just assume to - # take all conditions as inputs. - indices = condition_indices or range(len(self.conditions)) + # NOTE: If no indices are passed, we just assume to take all outputs + # as inputs. + indices = indices or range(len(self.outputs)) return [ - Fulfillment(self.conditions[cid].fulfillment, - self.conditions[cid].owners_after, - TransactionLink(self.id, cid)) - for cid in indices + Input(self.outputs[idx].fulfillment, + self.outputs[idx].public_keys, + TransactionLink(self.id, idx)) + for idx in indices ] - def add_fulfillment(self, fulfillment): - """Adds a Fulfillment to a Transaction's list of Fulfillments. + def add_input(self, input_): + """Adds an input to a Transaction's list of inputs. Args: - fulfillment (:class:`~bigchaindb.common.transaction. - Fulfillment`): A Fulfillment to be added to the - Transaction. + input_ (:class:`~bigchaindb.common.transaction. + Input`): An Input to be added to the Transaction. """ - if not isinstance(fulfillment, Fulfillment): - raise TypeError('`fulfillment` must be a Fulfillment instance') - self.fulfillments.append(fulfillment) + if not isinstance(input_, Input): + raise TypeError('`input_` must be a Input instance') + self.inputs.append(input_) - def add_condition(self, condition): - """Adds a Condition to a Transaction's list of Conditions. + def add_output(self, output): + """Adds an output to a Transaction's list of outputs. Args: - condition (:class:`~bigchaindb.common.transaction. - Condition`): A Condition to be added to the + output (:class:`~bigchaindb.common.transaction. + Output`): An Output 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) + if not isinstance(output, Output): + raise TypeError('`output` must be an Output instance or None') + self.outputs.append(output) def sign(self, private_keys): - """Fulfills a previous Transaction's Condition by signing Fulfillments. + """Fulfills a previous Transaction's Output by signing Inputs. Note: This method works only for the following Cryptoconditions @@ -684,23 +685,22 @@ class Transaction(object): key_pairs = {gen_public_key(PrivateKey(private_key)): PrivateKey(private_key) for private_key in private_keys} - for index, fulfillment in enumerate(self.fulfillments): - # NOTE: We clone the current transaction but only add the condition - # and fulfillment we're currently working on plus all + for index, input_ in enumerate(self.inputs): + # NOTE: We clone the current transaction but only add the output + # and input we're currently working on plus all # previously signed ones. - tx_partial = Transaction(self.operation, self.asset, [fulfillment], - self.conditions, self.metadata, + tx_partial = Transaction(self.operation, self.asset, [input_], + self.outputs, self.metadata, 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) + self._sign_input(input_, 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. + def _sign_input(self, input_, index, tx_serialized, key_pairs): + """Signs a single Input with a partial Transaction as message. Note: This method works only for the following Cryptoconditions @@ -709,67 +709,65 @@ class Transaction(object): - 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. + input_ (:class:`~bigchaindb.common.transaction. + Input`) The Input to be signed. + index (int): The index of the input 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, + if isinstance(input_.fulfillment, Ed25519Fulfillment): + self._sign_simple_signature_fulfillment(input_, index, tx_serialized, key_pairs) - elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): - self._sign_threshold_signature_fulfillment(fulfillment, index, + elif isinstance(input_.fulfillment, ThresholdSha256Fulfillment): + self._sign_threshold_signature_fulfillment(input_, 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, + def _sign_simple_signature_fulfillment(self, input_, 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 + input_ (:class:`~bigchaindb.common.transaction. + Input`) The input to be signed. + index (int): The index of the input 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 + # reference, we remove the reference of input_ 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] + input_ = deepcopy(input_) + public_key = input_.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]) + input_.fulfillment.sign(tx_serialized.encode(), key_pairs[public_key]) except KeyError: raise KeypairMismatchException('Public key {} is not a pair to ' 'any of the private keys' - .format(owner_before)) - self.fulfillments[index] = fulfillment + .format(public_key)) + self.inputs[index] = input_ - def _sign_threshold_signature_fulfillment(self, fulfillment, index, + def _sign_threshold_signature_fulfillment(self, input_, 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 + input_ (:class:`~bigchaindb.common.transaction. + Input`) The Input to be signed. + index (int): The index of the Input 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: + input_ = deepcopy(input_) + for owner_before in input_.owners_before: try: # TODO: CC should throw a KeypairMismatchException, instead of # our manual mapping here @@ -780,7 +778,7 @@ class Transaction(object): # TODO FOR CC: `get_subcondition` is singular. One would not # expect to get a list back. - ccffill = fulfillment.fulfillment + ccffill = input_.fulfillment subffill = ccffill.get_subcondition_from_vk(owner_before)[0] except IndexError: raise KeypairMismatchException('Public key {} cannot be found ' @@ -796,121 +794,119 @@ class Transaction(object): # 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 + self.inputs[index] = input_ - def fulfillments_valid(self, input_conditions=None): - """Validates the Fulfillments in the Transaction against given - Conditions. + def inputs_valid(self, outputs=None): + """Validates the Inputs in the Transaction against given + Outputs. Note: Given a `CREATE` or `GENESIS` Transaction is passed, - dummyvalues for Conditions are submitted for validation that + dummy values for Outputs 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. + outputs (:obj:`list` of :class:`~bigchaindb.common. + transaction.Output`): A list of Outputs to check the + Inputs against. Returns: - bool: If all Fulfillments are valid. + bool: If all Inputs 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 + # to check for outputs, 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]) + return self._inputs_valid(['dummyvalue' + for _ in self.inputs]) elif self.operation == Transaction.TRANSFER: - return self._fulfillments_valid([cond.fulfillment.condition_uri - for cond in input_conditions]) + return self._inputs_valid([output.fulfillment.condition_uri + for output in outputs]) 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. + def _inputs_valid(self, output_condition_uris): + """Validates an Input against a given set of Outputs. Note: - The number of `input_condition_uris` must be equal to the - number of Fulfillments a Transaction has. + The number of `output_condition_uris` must be equal to the + number of Inputs a Transaction has. Args: - input_condition_uris (:obj:`list` of :obj:`str`): A list of - Conditions to check the Fulfillments against. + output_condition_uris (:obj:`list` of :obj:`str`): A list of + Outputs to check the Inputs against. Returns: - bool: If all Fulfillments are valid. + bool: If all Outputs are valid. """ - input_condition_uris_count = len(input_condition_uris) - fulfillments_count = len(self.fulfillments) - def gen_tx(fulfillment, condition, input_condition_uri=None): + if len(self.inputs) != len(output_condition_uris): + raise ValueError('Inputs and ' + 'output_condition_uris must have the same count') + + def gen_tx(input_, output, output_condition_uri=None): """Splits multiple IO Transactions into partial single IO Transactions. """ - tx = Transaction(self.operation, self.asset, [fulfillment], - self.conditions, self.metadata, self.version) + tx = Transaction(self.operation, self.asset, [input_], + self.outputs, self.metadata, 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) + return self.__class__._input_valid(input_, + self.operation, + tx_serialized, + output_condition_uri) - if not fulfillments_count == input_condition_uris_count: - raise ValueError('Fulfillments and ' - 'input_condition_uris must have the same count') - - partial_transactions = map(gen_tx, self.fulfillments, - self.conditions, input_condition_uris) + partial_transactions = map(gen_tx, self.inputs, + self.outputs, output_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. + def _input_valid(input_, operation, tx_serialized, output_condition_uri=None): + """Validates a single Input against a single Output. Note: In case of a `CREATE` or `GENESIS` Transaction, this method - does not validate against `input_condition_uri`. + does not validate against `output_condition_uri`. Args: - fulfillment (:class:`~bigchaindb.common.transaction. - Fulfillment`) The Fulfillment to be signed. + input_ (:class:`~bigchaindb.common.transaction. + Input`) The Input 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. + output_condition_uri (str, optional): An Output to check the + Input against. Returns: - bool: If the Fulfillment is valid. + bool: If the Input is valid. """ - ccffill = fulfillment.fulfillment + ccffill = input_.fulfillment try: - parsed_ffill = CCFulfillment.from_uri(ccffill.serialize_uri()) + parsed_ffill = Fulfillment.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 + # output is always valid. + output_valid = True else: - input_cond_valid = input_condition_uri == ccffill.condition_uri + output_valid = output_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 + ffill_valid = parsed_ffill.validate(message=tx_serialized.encode(), + now=gen_timestamp()) + return output_valid and ffill_valid def to_dict(self): """Transforms the object to a Python dictionary. @@ -919,10 +915,8 @@ class Transaction(object): dict: The Transaction as an alternative serialization format. """ tx = { - 'fulfillments': [fulfillment.to_dict() for fulfillment - in self.fulfillments], - 'conditions': [condition.to_dict() for condition - in self.conditions], + 'inputs': [input_.to_dict() for input_ in self.inputs], + 'outputs': [output.to_dict() for output in self.outputs], 'operation': str(self.operation), 'metadata': self.metadata, 'asset': self.asset, @@ -951,12 +945,12 @@ class Transaction(object): # 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['fulfillments']: + for input_ in tx_dict['inputs']: # 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 + input_['fulfillment'] = None return tx_dict @staticmethod @@ -1047,10 +1041,7 @@ class Transaction(object): :class:`~bigchaindb.common.transaction.Transaction` """ cls.validate_structure(tx) - fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment - in tx['fulfillments']] - conditions = [Condition.from_dict(condition) for condition - in tx['conditions']] - - return cls(tx['operation'], tx['asset'], fulfillments, conditions, + inputs = [Input.from_dict(input_) for input_ in tx['inputs']] + outputs = [Output.from_dict(output) for output in tx['outputs']] + return cls(tx['operation'], tx['asset'], inputs, outputs, tx['metadata'], tx['version']) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index b1e87e00..a69780bb 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -355,7 +355,7 @@ class Bigchain(object): if cursor: return cursor[0]['asset'] - def get_spent(self, txid, cid): + def get_spent(self, txid, output): """Check if a `txid` was already used as an input. A transaction can be used as an input for another transaction. Bigchain needs to make sure that a @@ -363,15 +363,16 @@ class Bigchain(object): Args: txid (str): The id of the transaction - cid (num): the index of the condition in the respective transaction + output (num): the index of the output in the respective transaction Returns: The transaction (Transaction) that used the `txid` as an input else `None` """ # checks if an input was already spent - # checks if the bigchain has any transaction with input {'txid': ..., 'cid': ...} - transactions = list(backend.query.get_spent(self.connection, txid, cid)) + # checks if the bigchain has any transaction with input {'txid': ..., + # 'output': ...} + transactions = list(backend.query.get_spent(self.connection, txid, output)) # a transaction_id should have been spent at most one time if transactions: @@ -403,7 +404,7 @@ class Bigchain(object): owner (str): base58 encoded public key. Returns: - :obj:`list` of TransactionLink: list of ``txid`` s and ``cid`` s + :obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s pointing to another transaction's condition """ @@ -420,22 +421,22 @@ class Bigchain(object): # NOTE: It's OK to not serialize the transaction here, as we do not # use it after the execution of this function. - # a transaction can contain multiple outputs (conditions) so we need to iterate over all of them + # a transaction can contain multiple outputs so we need to iterate over all of them # to get a list of outputs available to spend - for index, cond in enumerate(tx['conditions']): + for index, output in enumerate(tx['outputs']): # for simple signature conditions there are no subfulfillments # check if the owner is in the condition `owners_after` - if len(cond['owners_after']) == 1: - if cond['condition']['details']['public_key'] == owner: + if len(output['public_keys']) == 1: + if output['condition']['details']['public_key'] == owner: tx_link = TransactionLink(tx['id'], index) else: - # for transactions with multiple `owners_after` there will be several subfulfillments nested + # for transactions with multiple `public_keys` there will be several subfulfillments nested # in the condition. We need to iterate the subfulfillments to make sure there is a # subfulfillment for `owner` - if util.condition_details_has_owner(cond['condition']['details'], owner): + if util.condition_details_has_owner(output['condition']['details'], owner): tx_link = TransactionLink(tx['id'], index) # check if input was already spent - if not self.get_spent(tx_link.txid, tx_link.cid): + if not self.get_spent(tx_link.txid, tx_link.output): owned.append(tx_link) return owned diff --git a/bigchaindb/models.py b/bigchaindb/models.py index 3f04682c..69b950e5 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -33,14 +33,14 @@ class Transaction(Transaction): InvalidHash: if the hash of the transaction is wrong InvalidSignature: if the signature of the transaction is wrong """ - if len(self.fulfillments) == 0: - raise ValueError('Transaction contains no fulfillments') + if len(self.inputs) == 0: + raise ValueError('Transaction contains no inputs') input_conditions = [] - inputs_defined = all([ffill.tx_input for ffill in self.fulfillments]) + inputs_defined = all([input_.fulfills for input_ in self.inputs]) # validate amounts - if any(condition.amount < 1 for condition in self.conditions): + if any(output.amount < 1 for output in self.outputs): raise AmountError('`amount` needs to be greater than zero') if self.operation in (Transaction.CREATE, Transaction.GENESIS): @@ -63,9 +63,8 @@ class Transaction(Transaction): # store the inputs so that we can check if the asset ids match input_txs = [] - for ffill in self.fulfillments: - input_txid = ffill.tx_input.txid - input_cid = ffill.tx_input.cid + for input_ in self.inputs: + input_txid = input_.fulfills.txid input_tx, status = bigchain.\ get_transaction(input_txid, include_status=True) @@ -78,15 +77,16 @@ class Transaction(Transaction): 'input `{}` does not exist in a valid block'.format( input_txid)) - spent = bigchain.get_spent(input_txid, ffill.tx_input.cid) + spent = bigchain.get_spent(input_txid, input_.fulfills.output) if spent and spent.id != self.id: raise DoubleSpend('input `{}` was already spent' .format(input_txid)) - input_condition = input_tx.conditions[input_cid] - input_conditions.append(input_condition) - + output = input_tx.outputs[input_.fulfills.output] + input_conditions.append(output) input_txs.append(input_tx) + if output.amount < 1: + raise AmountError('`amount` needs to be greater than zero') # validate asset id asset_id = Transaction.get_asset_id(input_txs) @@ -95,8 +95,13 @@ class Transaction(Transaction): ' match the asset id of the' ' transaction')) + # validate the amounts + for output in self.outputs: + if output.amount < 1: + raise AmountError('`amount` needs to be greater than zero') + input_amount = sum([input_condition.amount for input_condition in input_conditions]) - output_amount = sum([output_condition.amount for output_condition in self.conditions]) + output_amount = sum([output_condition.amount for output_condition in self.outputs]) if output_amount != input_amount: raise AmountError(('The amount used in the inputs `{}`' @@ -109,7 +114,7 @@ class Transaction(Transaction): raise TypeError('`operation`: `{}` must be either {}.' .format(self.operation, allowed_operations)) - if not self.fulfillments_valid(input_conditions): + if not self.inputs_valid(input_conditions): raise InvalidSignature() return self diff --git a/docs/root/source/immutable.md b/docs/root/source/immutable.md index a62f7e4e..28fb5999 100644 --- a/docs/root/source/immutable.md +++ b/docs/root/source/immutable.md @@ -11,7 +11,7 @@ BigchainDB achieves strong tamper-resistance in the following ways: 1. **Replication.** All data is sharded and shards are replicated in several (different) places. The replication factor can be set by the federation. The higher the replication factor, the more difficult it becomes to change or delete all replicas. 2. **Internal watchdogs.** All nodes monitor all changes and if some unallowed change happens, then appropriate action is taken. For example, if a valid block is deleted, then it is put back. 3. **External watchdogs.** Federations may opt to have trusted third-parties to monitor and audit their data, looking for irregularities. For federations with publicly-readable data, the public can act as an auditor. -4. **Cryptographic signatures** are used throughout BigchainDB as a way to check if messages (transactions, blocks and votes) have been tampered with enroute, and as a way to verify who signed the messages. Each block is signed by the node that created it. Each vote is signed by the node that cast it. A creation transaction is signed by the node that created it, although there are plans to improve that by adding signatures from the sending client and multiple nodes; see [Issue #347](https://github.com/bigchaindb/bigchaindb/issues/347). Transfer transactions can contain multiple fulfillments (one per asset transferred). Each fulfillment will typically contain one or more signatures from the owners (i.e. the owners before the transfer). Hashlock fulfillments are an exception; there’s an open issue ([#339](https://github.com/bigchaindb/bigchaindb/issues/339)) to address that. +4. **Cryptographic signatures** are used throughout BigchainDB as a way to check if messages (transactions, blocks and votes) have been tampered with enroute, and as a way to verify who signed the messages. Each block is signed by the node that created it. Each vote is signed by the node that cast it. A creation transaction is signed by the node that created it, although there are plans to improve that by adding signatures from the sending client and multiple nodes; see [Issue #347](https://github.com/bigchaindb/bigchaindb/issues/347). Transfer transactions can contain multiple inputs (fulfillments, one per asset transferred). Each fulfillment will typically contain one or more signatures from the owners (i.e. the owners before the transfer). Hashlock fulfillments are an exception; there’s an open issue ([#339](https://github.com/bigchaindb/bigchaindb/issues/339)) to address that. 5. **Full or partial backups** of the database may be recorded from time to time, possibly on magnetic tape storage, other blockchains, printouts, etc. 6. **Strong security.** Node owners can adopt and enforce strong security policies. 7. **Node diversity.** Diversity makes it so that no one thing (e.g. natural disaster or operating system bug) can compromise enough of the nodes. See [the section on the kinds of node diversity](diversity.html). diff --git a/docs/root/source/transaction-concepts.md b/docs/root/source/transaction-concepts.md index 139bf494..58d8a013 100644 --- a/docs/root/source/transaction-concepts.md +++ b/docs/root/source/transaction-concepts.md @@ -18,29 +18,31 @@ That means you can create/register an asset with an initial quantity, e.g. 700 oak trees. Divisible assets can be split apart or recombined by transfer transactions (described more below). -A CREATE transaction also establishes the conditions that must be met to -transfer the asset(s). For example, there may be a condition that any transfer -must be signed (cryptographically) by the private key associated with a -given public key. More sophisticated conditions are possible. -BigchainDB's conditions are based on the crypto-conditions of the [Interledger -Protocol (ILP)](https://interledger.org/). +A CREATE transaction also establishes, in its outputs, the conditions that must +be met to transfer the asset(s). The conditions may also be associated with a +list of public keys that, depending on the condition, may have full or partial +control over the asset(s). For example, there may be a condition that any +transfer must be signed (cryptographically) by the private key associated with a +given public key. More sophisticated conditions are possible. BigchainDB's +conditions are based on the crypto-conditions of the [Interledger Protocol +(ILP)](https://interledger.org/). ## TRANSFER Transactions A TRANSFER transaction can transfer an asset -by fulfilling the current conditions on the asset. +by providing inputs which fulfill the current output conditions on the asset. It must also specify new transfer conditions. **Example 1:** Suppose a red car is owned and controlled by Joe. Suppose the current transfer condition on the car says that any valid transfer must be signed by Joe. Joe and a buyer named Rae could build a TRANSFER transaction containing -Joe's signature (to fulfill the current transfer condition) -plus a new transfer condition saying that any valid transfer +an input with Joe's signature (to fulfill the current output condition) +plus a new output condition saying that any valid transfer must be signed by Rae. **Example 2:** Someone might construct a TRANSFER transaction -that fulfills the transfer conditions on four +that fulfills the output conditions on four previously-untransferred assets of the same asset type e.g. paperclips. The amounts might be 20, 10, 45 and 25, say, for a total of 100 paperclips. diff --git a/docs/server/generate_schema_documentation.py b/docs/server/generate_schema_documentation.py index 6b638d0a..9b6c1407 100644 --- a/docs/server/generate_schema_documentation.py +++ b/docs/server/generate_schema_documentation.py @@ -57,9 +57,9 @@ Transaction Schema * `Transaction`_ -* Condition_ +* Input_ -* Fulfillment_ +* Output_ * Asset_ @@ -71,15 +71,15 @@ Transaction %(transaction)s -Condition ----------- +Input +----- -%(condition)s +%(input)s -Fulfillment ------------ +Output +------ -%(fulfillment)s +%(output)s Asset ----- @@ -99,8 +99,8 @@ def generate_transaction_docs(): doc = TPL_TRANSACTION % { 'transaction': render_section('Transaction', schema), - 'condition': render_section('Condition', defs['condition']), - 'fulfillment': render_section('Fulfillment', defs['fulfillment']), + 'output': render_section('Output', defs['output']), + 'input': render_section('Input', defs['input']), 'asset': render_section('Asset', defs['asset']), 'metadata': render_section('Metadata', defs['metadata']['anyOf'][0]), 'container': 'transaction-schema', diff --git a/docs/server/source/data-models/crypto-conditions.md b/docs/server/source/data-models/crypto-conditions.md deleted file mode 100644 index 3ef868fb..00000000 --- a/docs/server/source/data-models/crypto-conditions.md +++ /dev/null @@ -1,126 +0,0 @@ -# Crypto-Conditions and Fulfillments - -To create a transaction that transfers an asset to new owners, one must fulfill the asset’s current conditions (crypto-conditions). The most basic kinds of conditions are: - -* **A hashlock condition:** One can fulfill a hashlock condition by providing the correct “preimage” (similar to a password or secret phrase) -* **A simple signature condition:** One can fulfill a simple signature condition by a providing a valid cryptographic signature (i.e. corresponding to the public key of an owner, usually) -* **A timeout condition:** Anyone can fulfill a timeout condition before the condition’s expiry time. After the expiry time, nobody can fulfill the condition. Another way to say this is that a timeout condition’s fulfillment is valid (TRUE) before the expiry time and invalid (FALSE) after the expiry time. Note: at the time of writing, timeout conditions are BigchainDB-specific (i.e. not part of the Interledger specs). - -A more complex condition can be composed by using n of the above conditions as inputs to an m-of-n threshold condition (a logic gate which outputs TRUE iff m or more inputs are TRUE). If there are n inputs to a threshold condition: -* 1-of-n is the same as a logical OR of all the inputs -* n-of-n is the same as a logical AND of all the inputs - -For example, one could create a condition requiring that m (of n) owners provide signatures before their asset can be transferred to new owners. - -One can also put different weights on the inputs to threshold condition, along with a threshold that the weighted-sum-of-inputs must pass for the output to be TRUE. Weights could be used, for example, to express the number of shares that someone owns in an asset. - -The (single) output of a threshold condition can be used as one of the inputs of other threshold conditions. This means that one can combine threshold conditions to build complex logical expressions, e.g. (x OR y) AND (u OR v). - -Aside: In BigchainDB, the output of an m-of-n threshold condition can be inverted on the way out, so an output that would have been TRUE would get changed to FALSE (and vice versa). This enables the creation of NOT, NOR and NAND gates. At the time of writing, this “inverted threshold condition” is BigchainDB-specific (i.e. not part of the Interledger specs). It should only be used in combination with a timeout condition. - -When one creates a condition, one can calculate its fulfillment length (e.g. 96). The more complex the condition, the larger its fulfillment length will be. A BigchainDB federation can put an upper limit on the allowed fulfillment length, as a way of capping the complexity of conditions (and the computing time required to validate them). - -If someone tries to make a condition where the output of a threshold condition feeds into the input of another “earlier” threshold condition (i.e. in a closed logical circuit), then their computer will take forever to calculate the (infinite) “condition URI”, at least in theory. In practice, their computer will run out of memory or their client software will timeout after a while. - -Aside: In what follows, the list of `owners_after` (in a condition) is always who owned the asset at the time the transaction completed, but before the next transaction started. The list of `owners_before` (in a fulfillment) is always equal to the list of `owners_after` in that asset's previous transaction. - -## (Crypto-) Conditions - -### One New Owner - -If there is only one _new owner_, the condition will be a simple signature condition (i.e. only one signature is required). - -```json -{ - "condition": { - "details": { - "bitmask": "", - "public_key": "", - "signature": null, - "type": "fulfillment", - "type_id": "" - }, - "uri": "" - }, - "owners_after": [""], - "amount": "" -} -``` - -- **Condition header**: - - `owners_after`: A list containing one item: the public key of the new owner. - - `amount`: The amount of shares for a divisible asset to send to the new owners. -- **Condition body**: - - `bitmask`: A set of bits representing the features required by the condition type. - - `public_key`: The new owner's public key. - - `type_id`: The fulfillment type ID; see the [ILP spec](https://interledger.org/five-bells-condition/spec.html). - - `uri`: Binary representation of the condition using only URL-safe characters. - -### Multiple New Owners - -If there are multiple _new owners_, they can create a ThresholdCondition requiring a signature from each of them in order -to spend the asset. For example: - -```json -{ - "condition": { - "details": { - "bitmask": 41, - "subfulfillments": [ - { - "bitmask": 32, - "public_key": "", - "signature": null, - "type": "fulfillment", - "type_id": 4, - "weight": 1 - }, - { - "bitmask": 32, - "public_key": "", - "signature": null, - "type": "fulfillment", - "type_id": 4, - "weight": 1 - } - ], - "threshold": 2, - "type": "fulfillment", - "type_id": 2 - }, - "uri": "cc:2:29:ytNK3X6-bZsbF-nCGDTuopUIMi1HCyCkyPewm6oLI3o:206"}, - "owners_after": [ - "owner 1 public key>", - "owner 2 public key>" - ] -} -``` - -- `subfulfillments`: a list of fulfillments - - `weight`: integer weight for each subfulfillment's contribution to the threshold -- `threshold`: threshold to reach for the subfulfillments to reach a valid fulfillment - -The `weight`s and `threshold` could be adjusted. For example, if the `threshold` was changed to 1 above, then only one of the new owners would have to provide a signature to spend the asset. - -## Fulfillments - -### One Current Owner - -If there is only one _current owner_, the fulfillment will be a simple signature fulfillment (i.e. containing just one signature). - -```json -{ - "owners_before": [""], - "fulfillment": "cf:4:RxFzIE679tFBk8zwEgizhmTuciAylvTUwy6EL6ehddHFJOhK5F4IjwQ1xLu2oQK9iyRCZJdfWAefZVjTt3DeG5j2exqxpGliOPYseNkRAWEakqJ_UrCwgnj92dnFRAEE", - "input": { - "cid": 0, - "txid": "11b3e7d893cc5fdfcf1a1706809c7def290a3b10b0bef6525d10b024649c42d3" - } -} -``` - -- `owners_before`: A list of public keys of the owners before the transaction; in this case it has just one public key. -- `fulfillment`: A crypto-conditions URI that encodes the cryptographic fulfillments like signatures and others, see [crypto-conditions](https://interledger.org/five-bells-condition/spec.html). -- `input`: Pointer to the asset and condition of a previous transaction - - `cid`: Condition index - the index of the condition in the array of conditions in the previous transaction - - `txid`: Transaction id diff --git a/docs/server/source/data-models/index.rst b/docs/server/source/data-models/index.rst index 70a4fae4..1e8b81c3 100644 --- a/docs/server/source/data-models/index.rst +++ b/docs/server/source/data-models/index.rst @@ -3,7 +3,7 @@ Data Models BigchainDB stores all data in the underlying database as JSON documents (conceptually, at least). There are three main kinds: -1. Transactions, which contain digital assets, conditions, fulfillments and other things +1. Transactions, which contain digital assets, inputs, outputs, and other things 2. Blocks 3. Votes @@ -14,6 +14,6 @@ This section unpacks each one in turn. transaction-model asset-model - crypto-conditions + inputs-outputs block-model vote-model diff --git a/docs/server/source/data-models/inputs-outputs.rst b/docs/server/source/data-models/inputs-outputs.rst new file mode 100644 index 00000000..46bcf6d0 --- /dev/null +++ b/docs/server/source/data-models/inputs-outputs.rst @@ -0,0 +1,129 @@ +Inputs and Outputs +================== + +BigchainDB is modelled around *assets*, and *inputs* and *outputs* are the mechanism by which control of an asset is transferred. + +Amounts of an asset are encoded in the outputs of a transaction, and each output may be spent separately. In order to spend an output, the output's ``conditions`` must be met by an ``input`` that provides corresponding ``fulfillments``. Each output may be spent at most once, by a single input. Note that any asset associated with an output holding an amount greater than one is considered a divisible asset that may be split up in future transactions. + +.. note:: + + This document (and various places in the BigchainDB documentation and code) talks about control of an asset in terms of *owners* and *ownership*. The language is chosen to represent the most common use cases, but in some more complex scenarios, it may not be accurate to say that the output is owned by the controllers of those public keys–it would only be correct to say that those public keys are associated with the ability to fulfill the output. Also, depending on the use case, the entity controlling an output via a private key may not be the legal owner of the asset in the corresponding legal domain. However, since we aim to use language that is simple to understand and covers the majority of use cases, we talk in terms of *owners* of an output that have the ability to *spend* that output. + +In the most basic case, an output may define a **simple signature condition**, which gives control of the output to the entity controlling a corresponding private key. + +A more complex condition can be composed by using n of the above conditions as inputs to an m-of-n threshold condition (a logic gate which outputs TRUE if and only if m or more inputs are TRUE). If there are n inputs to a threshold condition: + +* 1-of-n is the same as a logical OR of all the inputs +* n-of-n is the same as a logical AND of all the inputs + +For example, one could create a condition requiring m (of n) signatures before their asset can be transferred. + +One can also put different weights on the inputs to a threshold condition, along with a threshold that the weighted-sum-of-inputs must pass for the output to be TRUE. + +The (single) output of a threshold condition can be used as one of the inputs of other threshold conditions. This means that one can combine threshold conditions to build complex logical expressions, e.g. (x OR y) AND (u OR v). + +When one creates a condition, one can calculate its fulfillment length (e.g. 96). The more complex the condition, the larger its fulfillment length will be. A BigchainDB federation can put an upper limit on the allowed fulfillment length, as a way of capping the complexity of conditions (and the computing time required to validate them). + +If someone tries to make a condition where the output of a threshold condition feeds into the input of another “earlier” threshold condition (i.e. in a closed logical circuit), then their computer will take forever to calculate the (infinite) “condition URI”, at least in theory. In practice, their computer will run out of memory or their client software will timeout after a while. + +Outputs +------- + +.. note:: + + In what follows, the list of ``public_keys`` (in a condition) is always the controllers of the asset at the time the transaction completed, but before the next transaction started. The list of ``owners_before`` (in an input) is always equal to the list of ``public_keys`` in that asset's previous transaction. + +One New Owner +````````````` + +If there is only one *new owner*, the output will contain a simple signature condition (i.e. only one signature is required). + +.. code-block:: json + + { + "condition": { + "details": { + "bitmask": "", + "public_key": "", + "signature": null, + "type": "fulfillment", + "type_id": "" + }, + "uri": "" + }, + "public_keys": [""], + "amount": "" + } + + +See the reference on :ref:`outputs ` for descriptions of the meaning of each field. + +Multiple New Owners +``````````````````` + +If there are multiple *new owners*, they can create a ThresholdCondition requiring a signature from each of them in order +to spend the asset. For example: + +.. code-block:: json + + { + "condition": { + "details": { + "bitmask": 41, + "subfulfillments": [ + { + "bitmask": 32, + "public_key": "", + "signature": null, + "type": "fulfillment", + "type_id": 4, + "weight": 1 + }, + { + "bitmask": 32, + "public_key": "", + "signature": null, + "type": "fulfillment", + "type_id": 4, + "weight": 1 + } + ], + "threshold": 2, + "type": "fulfillment", + "type_id": 2 + }, + "uri": "cc:2:29:ytNK3X6-bZsbF-nCGDTuopUIMi1HCyCkyPewm6oLI3o:206"}, + "public_keys": [ + "", + "" + ] + } + + +- ``subfulfillments``: a list of fulfillments + - ``weight``: integer weight for each subfulfillment's contribution to the threshold +- ``threshold``: threshold to reach for the subfulfillments to reach a valid fulfillment + +The ``weight``s and ``threshold`` could be adjusted. For example, if the ``threshold`` was changed to 1 above, then only one of the new owners would have to provide a signature to spend the asset. + +Inputs +------ + +One Current Owner +````````````````` + +If there is only one *current owner*, the fulfillment will be a simple signature fulfillment (i.e. containing just one signature). + +.. code-block:: json + + { + "owners_before": [""], + "fulfillment": "cf:4:RxFzIE679tFBk8zwEgizhmTuciAylvTUwy6EL6ehddHFJOhK5F4IjwQ1xLu2oQK9iyRCZJdfWAefZVjTt3DeG5j2exqxpGliOPYseNkRAWEakqJ_UrCwgnj92dnFRAEE", + "fulfills": { + "output": 0, + "txid": "11b3e7d893cc5fdfcf1a1706809c7def290a3b10b0bef6525d10b024649c42d3" + } + } + + +See the reference on :ref:`inputs ` for descriptions of the meaning of each field. diff --git a/docs/server/source/data-models/transaction-model.rst b/docs/server/source/data-models/transaction-model.rst index 69a3b168..0ce10d03 100644 --- a/docs/server/source/data-models/transaction-model.rst +++ b/docs/server/source/data-models/transaction-model.rst @@ -22,8 +22,8 @@ A transaction has the following structure: { "id": "", "version": "", - "fulfillments": [""], - "conditions": [""], + "inputs": [""], + "outputs": [""], "operation": "", "asset": "", "metadata": "" @@ -33,13 +33,13 @@ Here's some explanation of the contents of a :ref:`transaction `: - id: The :ref:`id ` of the transaction, and also the database primary key. - version: :ref:`Version ` number of the transaction model, so that software can support different transaction models. -- **fulfillments**: List of fulfillments. Each :ref:`fulfillment ` contains a pointer to an unspent asset - and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment* +- **inputs**: List of inputs. Each :ref:`input ` contains a pointer to an unspent output + and a *crypto fulfillment* that satisfies the conditions of that output. A *fulfillment* is usually a signature proving the ownership of the asset. - See :doc:`./crypto-conditions`. + See :doc:`./inputs-outputs`. -- **conditions**: List of conditions. Each :ref:`condition ` is a *crypto-condition* that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners. - See :doc:`./crypto-conditions`. +- **outputs**: List of outputs. Each :ref:`output ` contains *crypto-conditions* that need to be fulfilled by a transfer transaction in order to transfer ownership to new owners. + See :doc:`./inputs-outputs`. - **operation**: String representation of the :ref:`operation ` being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated. @@ -47,6 +47,6 @@ Here's some explanation of the contents of a :ref:`transaction `: - **metadata**: User-provided transaction :ref:`metadata `: Can be any JSON document, or `NULL`. -Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the ``fulfillment`` string of each fulfillment. A creation transaction is signed by whoever created it. A transfer transaction is signed by whoever currently controls or owns it. +Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the ``fulfillment`` string of each input. A creation transaction is signed by whoever created it. A transfer transaction is signed by whoever currently controls or owns it. -What gets signed? For each fulfillment in the transaction, the "fullfillment message" that gets signed includes the ``operation``, ``data``, ``version``, ``id``, corresponding ``condition``, and the fulfillment itself, except with its fulfillment string set to ``null``. The computed signature goes into creating the ``fulfillment`` string of the fulfillment. +What gets signed? For each input in the transaction, the "fullfillment message" that gets signed includes the ``operation``, ``data``, ``version``, ``id``, corresponding ``condition``, and the fulfillment itself, except with its fulfillment string set to ``null``. The computed signature goes into creating the ``fulfillment`` string of the input. diff --git a/docs/server/source/drivers-clients/http-client-server-api.rst b/docs/server/source/drivers-clients/http-client-server-api.rst index 8d591ecb..969d912b 100644 --- a/docs/server/source/drivers-clients/http-client-server-api.rst +++ b/docs/server/source/drivers-clients/http-client-server-api.rst @@ -133,14 +133,14 @@ GET /unspents/ .. http:get:: /unspents?owner_after={owner_after} - Get a list of links to transactions' conditions that have not been used in - a previous transaction and could hence be called unspent conditions/outputs + Get a list of links to transactions' outputs that have not been used in + a previous transaction and could hence be called unspent outputs (or simply: unspents). This endpoint will return a ``HTTP 400 Bad Request`` if the querystring ``owner_after`` happens to not be defined in the request. - Note that if unspents for a certain ``owner_after`` have not been found by + Note that if unspents for a certain ``public_key`` have not been found by the server, this will result in the server returning a 200 OK HTTP status code and an empty list in the response's body. @@ -162,8 +162,8 @@ GET /unspents/ Content-Type: application/json [ - "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0", - "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1" + "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/0", + "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/1" ] :statuscode 200: A list of outputs were found and returned in the body of the response. diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 0ef1572d..160d93d2 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -188,7 +188,7 @@ def test_create_invalid_divisible_asset(b, user_pk, user_sk): # Asset amount must be more than 0 tx = Transaction.create([user_pk], [([user_pk], 1)]) - tx.conditions[0].amount = 0 + tx.outputs[0].amount = 0 tx.sign([user_sk]) with pytest.raises(AmountError): diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py index 1a2993c8..31e7890f 100644 --- a/tests/assets/test_divisible_assets.py +++ b/tests/assets/test_divisible_assets.py @@ -13,9 +13,9 @@ def test_single_in_single_own_single_out_single_own_create(b, user_pk): tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed - assert len(tx_signed.conditions) == 1 - assert tx_signed.conditions[0].amount == 100 - assert len(tx_signed.fulfillments) == 1 + assert len(tx_signed.outputs) == 1 + assert tx_signed.outputs[0].amount == 100 + assert len(tx_signed.inputs) == 1 # CREATE divisible asset @@ -30,10 +30,10 @@ def test_single_in_single_own_multiple_out_single_own_create(b, user_pk): tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed - assert len(tx_signed.conditions) == 2 - assert tx_signed.conditions[0].amount == 50 - assert tx_signed.conditions[1].amount == 50 - assert len(tx_signed.fulfillments) == 1 + assert len(tx_signed.outputs) == 2 + assert tx_signed.outputs[0].amount == 50 + assert tx_signed.outputs[1].amount == 50 + assert len(tx_signed.inputs) == 1 # CREATE divisible asset @@ -48,14 +48,14 @@ def test_single_in_single_own_single_out_multiple_own_create(b, user_pk): tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed - assert len(tx_signed.conditions) == 1 - assert tx_signed.conditions[0].amount == 100 + assert len(tx_signed.outputs) == 1 + assert tx_signed.outputs[0].amount == 100 - condition = tx_signed.conditions[0].to_dict() - assert 'subfulfillments' in condition['condition']['details'] - assert len(condition['condition']['details']['subfulfillments']) == 2 + output = tx_signed.outputs[0].to_dict() + assert 'subfulfillments' in output['condition']['details'] + assert len(output['condition']['details']['subfulfillments']) == 2 - assert len(tx_signed.fulfillments) == 1 + assert len(tx_signed.inputs) == 1 # CREATE divisible asset @@ -71,15 +71,15 @@ def test_single_in_single_own_multiple_out_mix_own_create(b, user_pk): tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed - assert len(tx_signed.conditions) == 2 - assert tx_signed.conditions[0].amount == 50 - assert tx_signed.conditions[1].amount == 50 + assert len(tx_signed.outputs) == 2 + assert tx_signed.outputs[0].amount == 50 + assert tx_signed.outputs[1].amount == 50 - condition_cid1 = tx_signed.conditions[1].to_dict() - assert 'subfulfillments' in condition_cid1['condition']['details'] - assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 + output_cid1 = tx_signed.outputs[1].to_dict() + assert 'subfulfillments' in output_cid1['condition']['details'] + assert len(output_cid1['condition']['details']['subfulfillments']) == 2 - assert len(tx_signed.fulfillments) == 1 + assert len(tx_signed.inputs) == 1 # CREATE divisible asset @@ -93,11 +93,11 @@ def test_single_in_multiple_own_single_out_single_own_create(b, user_pk, tx = Transaction.create([b.me, user_pk], [([user_pk], 100)]) tx_signed = tx.sign([b.me_private, user_sk]) assert tx_signed.validate(b) == tx_signed - assert len(tx_signed.conditions) == 1 - assert tx_signed.conditions[0].amount == 100 - assert len(tx_signed.fulfillments) == 1 + assert len(tx_signed.outputs) == 1 + assert tx_signed.outputs[0].amount == 100 + assert len(tx_signed.inputs) == 1 - ffill = tx_signed.fulfillments[0].fulfillment.to_dict() + ffill = tx_signed.inputs[0].fulfillment.to_dict() assert 'subfulfillments' in ffill assert len(ffill['subfulfillments']) == 2 @@ -134,9 +134,9 @@ def test_single_in_single_own_single_out_single_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 100 - assert len(tx_transfer_signed.fulfillments) == 1 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 100 + assert len(tx_transfer_signed.inputs) == 1 # TRANSFER divisible asset @@ -168,10 +168,10 @@ def test_single_in_single_own_multiple_out_single_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 2 - assert tx_transfer_signed.conditions[0].amount == 50 - assert tx_transfer_signed.conditions[1].amount == 50 - assert len(tx_transfer_signed.fulfillments) == 1 + assert len(tx_transfer_signed.outputs) == 2 + assert tx_transfer_signed.outputs[0].amount == 50 + assert tx_transfer_signed.outputs[1].amount == 50 + assert len(tx_transfer_signed.inputs) == 1 # TRANSFER divisible asset @@ -203,14 +203,14 @@ def test_single_in_single_own_single_out_multiple_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 100 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 100 - condition = tx_transfer_signed.conditions[0].to_dict() + condition = tx_transfer_signed.outputs[0].to_dict() assert 'subfulfillments' in condition['condition']['details'] assert len(condition['condition']['details']['subfulfillments']) == 2 - assert len(tx_transfer_signed.fulfillments) == 1 + assert len(tx_transfer_signed.inputs) == 1 # TRANSFER divisible asset @@ -243,15 +243,15 @@ def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 2 - assert tx_transfer_signed.conditions[0].amount == 50 - assert tx_transfer_signed.conditions[1].amount == 50 + assert len(tx_transfer_signed.outputs) == 2 + assert tx_transfer_signed.outputs[0].amount == 50 + assert tx_transfer_signed.outputs[1].amount == 50 - condition_cid1 = tx_transfer_signed.conditions[1].to_dict() - assert 'subfulfillments' in condition_cid1['condition']['details'] - assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 + output_cid1 = tx_transfer_signed.outputs[1].to_dict() + assert 'subfulfillments' in output_cid1['condition']['details'] + assert len(output_cid1['condition']['details']['subfulfillments']) == 2 - assert len(tx_transfer_signed.fulfillments) == 1 + assert len(tx_transfer_signed.inputs) == 1 # TRANSFER divisible asset @@ -282,11 +282,11 @@ def test_single_in_multiple_own_single_out_single_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 100 - assert len(tx_transfer_signed.fulfillments) == 1 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 100 + assert len(tx_transfer_signed.inputs) == 1 - ffill = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() + ffill = tx_transfer_signed.inputs[0].fulfillment.to_dict() assert 'subfulfillments' in ffill assert len(ffill['subfulfillments']) == 2 @@ -319,9 +319,9 @@ def test_multiple_in_single_own_single_out_single_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 100 - assert len(tx_transfer_signed.fulfillments) == 2 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 100 + assert len(tx_transfer_signed.inputs) == 2 # TRANSFER divisible asset @@ -352,12 +352,12 @@ def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 100 - assert len(tx_transfer_signed.fulfillments) == 2 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 100 + assert len(tx_transfer_signed.inputs) == 2 - ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() - ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() + ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict() + ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict() assert 'subfulfillments' in ffill_fid0 assert 'subfulfillments' in ffill_fid1 assert len(ffill_fid0['subfulfillments']) == 2 @@ -393,12 +393,12 @@ def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 100 - assert len(tx_transfer_signed.fulfillments) == 2 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 100 + assert len(tx_transfer_signed.inputs) == 2 - ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() - ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() + ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict() + ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict() assert 'subfulfillments' not in ffill_fid0 assert 'subfulfillments' in ffill_fid1 assert len(ffill_fid1['subfulfillments']) == 2 @@ -435,19 +435,19 @@ def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_pk, tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 2 - assert tx_transfer_signed.conditions[0].amount == 50 - assert tx_transfer_signed.conditions[1].amount == 50 - assert len(tx_transfer_signed.fulfillments) == 2 + assert len(tx_transfer_signed.outputs) == 2 + assert tx_transfer_signed.outputs[0].amount == 50 + assert tx_transfer_signed.outputs[1].amount == 50 + assert len(tx_transfer_signed.inputs) == 2 - cond_cid0 = tx_transfer_signed.conditions[0].to_dict() - cond_cid1 = tx_transfer_signed.conditions[1].to_dict() + cond_cid0 = tx_transfer_signed.outputs[0].to_dict() + cond_cid1 = tx_transfer_signed.outputs[1].to_dict() assert 'subfulfillments' not in cond_cid0['condition']['details'] assert 'subfulfillments' in cond_cid1['condition']['details'] assert len(cond_cid1['condition']['details']['subfulfillments']) == 2 - ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() - ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() + ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict() + ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict() assert 'subfulfillments' not in ffill_fid0 assert 'subfulfillments' in ffill_fid1 assert len(ffill_fid1['subfulfillments']) == 2 @@ -502,12 +502,12 @@ def test_multiple_in_different_transactions(b, user_pk, user_sk): tx_transfer2_signed = tx_transfer2.sign([user_sk]) assert tx_transfer2_signed.validate(b) == tx_transfer2_signed - assert len(tx_transfer2_signed.conditions) == 1 - assert tx_transfer2_signed.conditions[0].amount == 100 - assert len(tx_transfer2_signed.fulfillments) == 2 + assert len(tx_transfer2_signed.outputs) == 1 + assert tx_transfer2_signed.outputs[0].amount == 100 + assert len(tx_transfer2_signed.inputs) == 2 - fid0_input = tx_transfer2_signed.fulfillments[0].to_dict()['input']['txid'] - fid1_input = tx_transfer2_signed.fulfillments[1].to_dict()['input']['txid'] + fid0_input = tx_transfer2_signed.inputs[0].fulfills.txid + fid1_input = tx_transfer2_signed.inputs[1].fulfills.txid assert fid0_input == tx_create.id assert fid1_input == tx_transfer1.id @@ -604,8 +604,8 @@ def test_sum_amount(b, user_pk, user_sk): tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 1 - assert tx_transfer_signed.conditions[0].amount == 3 + assert len(tx_transfer_signed.outputs) == 1 + assert tx_transfer_signed.outputs[0].amount == 3 @pytest.mark.bdb @@ -632,9 +632,9 @@ def test_divide(b, user_pk, user_sk): tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert len(tx_transfer_signed.conditions) == 3 - for condition in tx_transfer_signed.conditions: - assert condition.amount == 1 + assert len(tx_transfer_signed.outputs) == 3 + for output in tx_transfer_signed.outputs: + assert output.amount == 1 # Check that negative inputs are caught when creating a TRANSFER transaction @@ -684,7 +684,7 @@ def test_non_positive_amounts_on_transfer_validate(b, user_pk, user_sk): tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 4), ([b.me], 1)], asset_id=tx_create.id) - tx_transfer.conditions[1].amount = -1 + tx_transfer.outputs[1].amount = -1 tx_transfer_signed = tx_transfer.sign([user_sk]) with pytest.raises(AmountError): @@ -712,7 +712,7 @@ def test_non_positive_amounts_on_create_validate(b, user_pk): # CREATE divisible asset with 1 output with amount 3 tx_create = Transaction.create([b.me], [([user_pk], 3)]) - tx_create.conditions[0].amount = -3 + tx_create.outputs[0].amount = -3 tx_create_signed = tx_create.sign([b.me_private]) with pytest.raises(AmountError): diff --git a/tests/common/conftest.py b/tests/common/conftest.py index 77cf6264..e8c4f9c6 100644 --- a/tests/common/conftest.py +++ b/tests/common/conftest.py @@ -93,39 +93,39 @@ def user2_Ed25519(user2_pub): @pytest.fixture -def user_ffill(user_Ed25519, user_pub): - from bigchaindb.common.transaction import Fulfillment - return Fulfillment(user_Ed25519, [user_pub]) +def user_input(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Input + return Input(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]) +def user2_input(user2_Ed25519, user2_pub): + from bigchaindb.common.transaction import Input + return Input(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]) +def user_user2_threshold_output(user_user2_threshold, user_pub, user2_pub): + from bigchaindb.common.transaction import Output + return Output(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]) +def user_user2_threshold_input(user_user2_threshold, user_pub, user2_pub): + from bigchaindb.common.transaction import Input + return Input(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]) +def user_output(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Output + return Output(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]) +def user2_output(user2_Ed25519, user2_pub): + from bigchaindb.common.transaction import Output + return Output(user2_Ed25519, [user2_pub]) @pytest.fixture @@ -144,9 +144,10 @@ def data(): @pytest.fixture -def utx(user_ffill, user_cond): +def utx(user_input, user_output): from bigchaindb.common.transaction import Transaction - return Transaction(Transaction.CREATE, {'data': None}, [user_ffill], [user_cond]) + return Transaction(Transaction.CREATE, {'data': None}, [user_input], + [user_output]) @pytest.fixture @@ -155,14 +156,14 @@ def tx(utx, user_priv): @pytest.fixture -def transfer_utx(user_cond, user2_cond, utx): - from bigchaindb.common.transaction import (Fulfillment, TransactionLink, +def transfer_utx(user_output, user2_output, utx): + from bigchaindb.common.transaction import (Input, TransactionLink, Transaction) - user_cond = user_cond.to_dict() - ffill = Fulfillment(utx.conditions[0].fulfillment, - user_cond['owners_after'], - TransactionLink(utx.id, 0)) - return Transaction('TRANSFER', {'id': utx.id}, [ffill], [user2_cond]) + user_output = user_output.to_dict() + input = Input(utx.outputs[0].fulfillment, + user_output['public_keys'], + TransactionLink(utx.id, 0)) + return Transaction('TRANSFER', {'id': utx.id}, [input], [user2_output]) @pytest.fixture diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index 4acd8f0e..7ed70add 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -1,111 +1,110 @@ from pytest import raises -def test_fulfillment_serialization(ffill_uri, user_pub): - from bigchaindb.common.transaction import Fulfillment - from cryptoconditions import Fulfillment as CCFulfillment +def test_input_serialization(ffill_uri, user_pub): + from bigchaindb.common.transaction import Input + from cryptoconditions import Fulfillment expected = { 'owners_before': [user_pub], 'fulfillment': ffill_uri, - 'input': None, + 'fulfills': None, } - ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) - assert ffill.to_dict() == expected + input = Input(Fulfillment.from_uri(ffill_uri), [user_pub]) + assert input.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 +def test_input_deserialization_with_uri(ffill_uri, user_pub): + from bigchaindb.common.transaction import Input + from cryptoconditions import Fulfillment - expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) + expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub]) ffill = { 'owners_before': [user_pub], 'fulfillment': ffill_uri, - 'input': None, + 'fulfills': None, } - ffill = Fulfillment.from_dict(ffill) + input = Input.from_dict(ffill) - assert ffill == expected + assert input == expected -def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): - from bigchaindb.common.transaction import Fulfillment +def test_input_deserialization_with_invalid_input(user_pub): + from bigchaindb.common.transaction import Input ffill = { 'owners_before': [user_pub], 'fulfillment': None, - 'input': None, + 'fulfills': None, } with raises(TypeError): - Fulfillment.from_dict(ffill) + Input.from_dict(ffill) -def test_fulfillment_deserialization_with_invalid_fulfillment_uri(user_pub): +def test_input_deserialization_with_invalid_fulfillment_uri(user_pub): from bigchaindb.common.exceptions import InvalidSignature - from bigchaindb.common.transaction import Fulfillment + from bigchaindb.common.transaction import Input ffill = { 'owners_before': [user_pub], 'fulfillment': 'an invalid fulfillment', - 'input': None, + 'fulfills': None, } with raises(InvalidSignature): - Fulfillment.from_dict(ffill) + Input.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 +def test_input_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub): + from bigchaindb.common.transaction import Input + from cryptoconditions import Fulfillment - expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) + expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub]) ffill = { 'owners_before': [user_pub], - 'fulfillment': CCFulfillment.from_uri(ffill_uri), - 'input': None, + 'fulfillment': Fulfillment.from_uri(ffill_uri), + 'fulfills': None, } - ffill = Fulfillment.from_dict(ffill) + input = Input.from_dict(ffill) - assert ffill == expected + assert input == expected -def test_condition_serialization(user_Ed25519, user_pub): - from bigchaindb.common.transaction import Condition +def test_output_serialization(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Output expected = { 'condition': { 'uri': user_Ed25519.condition_uri, 'details': user_Ed25519.to_dict(), }, - 'owners_after': [user_pub], + 'public_keys': [user_pub], 'amount': 1, } - cond = Condition(user_Ed25519, [user_pub], 1) + cond = Output(user_Ed25519, [user_pub], 1) assert cond.to_dict() == expected -def test_condition_deserialization(user_Ed25519, user_pub): - from bigchaindb.common.transaction import Condition +def test_output_deserialization(user_Ed25519, user_pub): + from bigchaindb.common.transaction import Output - expected = Condition(user_Ed25519, [user_pub], 1) + expected = Output(user_Ed25519, [user_pub], 1) cond = { 'condition': { 'uri': user_Ed25519.condition_uri, 'details': user_Ed25519.to_dict() }, - 'owners_after': [user_pub], + 'public_keys': [user_pub], 'amount': 1, } - cond = Condition.from_dict(cond) + cond = Output.from_dict(cond) assert cond == expected -def test_condition_hashlock_serialization(): - from bigchaindb.common.transaction import Condition +def test_output_hashlock_serialization(): + from bigchaindb.common.transaction import Output from cryptoconditions import PreimageSha256Fulfillment secret = b'wow much secret' @@ -115,49 +114,49 @@ def test_condition_hashlock_serialization(): 'condition': { 'uri': hashlock, }, - 'owners_after': None, + 'public_keys': None, 'amount': 1, } - cond = Condition(hashlock, amount=1) + cond = Output(hashlock, amount=1) assert cond.to_dict() == expected -def test_condition_hashlock_deserialization(): - from bigchaindb.common.transaction import Condition +def test_output_hashlock_deserialization(): + from bigchaindb.common.transaction import Output from cryptoconditions import PreimageSha256Fulfillment secret = b'wow much secret' hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri - expected = Condition(hashlock, amount=1) + expected = Output(hashlock, amount=1) cond = { 'condition': { 'uri': hashlock }, - 'owners_after': None, + 'public_keys': None, 'amount': 1, } - cond = Condition.from_dict(cond) + cond = Output.from_dict(cond) assert cond == expected -def test_invalid_condition_initialization(cond_uri, user_pub): - from bigchaindb.common.transaction import Condition +def test_invalid_output_initialization(cond_uri, user_pub): + from bigchaindb.common.transaction import Output from bigchaindb.common.exceptions import AmountError with raises(TypeError): - Condition(cond_uri, user_pub) + Output(cond_uri, user_pub) with raises(TypeError): - Condition(cond_uri, [user_pub], 'amount') + Output(cond_uri, [user_pub], 'amount') with raises(AmountError): - Condition(cond_uri, [user_pub], 0) + Output(cond_uri, [user_pub], 0) -def test_generate_conditions_split_half_recursive(user_pub, user2_pub, +def test_generate_output_split_half_recursive(user_pub, user2_pub, user3_pub): - from bigchaindb.common.transaction import Condition + from bigchaindb.common.transaction import Output from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -171,13 +170,13 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub, expected_threshold.add_subfulfillment(expected_simple3) expected.add_subfulfillment(expected_threshold) - cond = Condition.generate([user_pub, [user2_pub, expected_simple3]], 1) + cond = Output.generate([user_pub, [user2_pub, expected_simple3]], 1) assert cond.fulfillment.to_dict() == expected.to_dict() -def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, +def test_generate_outputs_split_half_single_owner(user_pub, user2_pub, user3_pub): - from bigchaindb.common.transaction import Condition + from bigchaindb.common.transaction import Output from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -191,12 +190,12 @@ def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, expected.add_subfulfillment(expected_threshold) expected.add_subfulfillment(expected_simple1) - cond = Condition.generate([[expected_simple2, user3_pub], user_pub], 1) + cond = Output.generate([[expected_simple2, user3_pub], user_pub], 1) 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 +def test_generate_outputs_flat_ownage(user_pub, user2_pub, user3_pub): + from bigchaindb.common.transaction import Output from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -208,42 +207,42 @@ def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub): expected.add_subfulfillment(expected_simple2) expected.add_subfulfillment(expected_simple3) - cond = Condition.generate([user_pub, user2_pub, expected_simple3], 1) + cond = Output.generate([user_pub, user2_pub, expected_simple3], 1) assert cond.fulfillment.to_dict() == expected.to_dict() -def test_generate_conditions_single_owner(user_pub): - from bigchaindb.common.transaction import Condition +def test_generate_output_single_owner(user_pub): + from bigchaindb.common.transaction import Output from cryptoconditions import Ed25519Fulfillment expected = Ed25519Fulfillment(public_key=user_pub) - cond = Condition.generate([user_pub], 1) + cond = Output.generate([user_pub], 1) assert cond.fulfillment.to_dict() == expected.to_dict() -def test_generate_conditions_single_owner_with_condition(user_pub): - from bigchaindb.common.transaction import Condition +def test_generate_output_single_owner_with_output(user_pub): + from bigchaindb.common.transaction import Output from cryptoconditions import Ed25519Fulfillment expected = Ed25519Fulfillment(public_key=user_pub) - cond = Condition.generate([expected], 1) + cond = Output.generate([expected], 1) assert cond.fulfillment.to_dict() == expected.to_dict() -def test_generate_conditions_invalid_parameters(user_pub, user2_pub, +def test_generate_output_invalid_parameters(user_pub, user2_pub, user3_pub): - from bigchaindb.common.transaction import Condition + from bigchaindb.common.transaction import Output with raises(ValueError): - Condition.generate([], 1) + Output.generate([], 1) with raises(TypeError): - Condition.generate('not a list', 1) + Output.generate('not a list', 1) with raises(ValueError): - Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1) + Output.generate([[user_pub, [user2_pub, [user3_pub]]]], 1) with raises(ValueError): - Condition.generate([[user_pub]], 1) + Output.generate([[user_pub]], 1) def test_invalid_transaction_initialization(asset_definition): @@ -259,21 +258,21 @@ def test_invalid_transaction_initialization(asset_definition): Transaction( operation='CREATE', asset=asset_definition, - conditions='invalid conditions' + outputs='invalid outputs' ) with raises(TypeError): Transaction( operation='CREATE', asset=asset_definition, - conditions=[], - fulfillments='invalid fulfillments' + outputs=[], + inputs='invalid inputs' ) with raises(TypeError): Transaction( operation='CREATE', asset=asset_definition, - conditions=[], - fulfillments=[], + outputs=[], + inputs=[], metadata='invalid metadata' ) @@ -288,18 +287,19 @@ def test_create_default_asset_on_tx_initialization(asset_definition): assert asset == expected -def test_transaction_serialization(user_ffill, user_cond, data): +def test_transaction_serialization(user_input, user_output, data): from bigchaindb.common.transaction import Transaction + from .util import validate_transaction_model tx_id = 'l0l' expected = { 'id': tx_id, 'version': Transaction.VERSION, - # NOTE: This test assumes that Fulfillments and Conditions can + # NOTE: This test assumes that Inputs and Outputs can # successfully be serialized - 'fulfillments': [user_ffill.to_dict()], - 'conditions': [user_cond.to_dict()], + 'inputs': [user_input.to_dict()], + 'outputs': [user_output.to_dict()], 'operation': Transaction.CREATE, 'metadata': None, 'asset': { @@ -307,28 +307,28 @@ def test_transaction_serialization(user_ffill, user_cond, data): } } - tx = Transaction(Transaction.CREATE, {'data': data}, [user_ffill], - [user_cond]) + tx = Transaction(Transaction.CREATE, {'data': data}, [user_input], + [user_output]) tx_dict = tx.to_dict() tx_dict['id'] = tx_id assert tx_dict == expected -def test_transaction_deserialization(user_ffill, user_cond, data): +def test_transaction_deserialization(user_input, user_output, data): from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model expected_asset = {'data': data} - expected = Transaction(Transaction.CREATE, expected_asset, [user_ffill], - [user_cond], None, Transaction.VERSION) + expected = Transaction(Transaction.CREATE, expected_asset, [user_input], + [user_output], None, Transaction.VERSION) tx = { 'version': Transaction.VERSION, - # NOTE: This test assumes that Fulfillments and Conditions can + # NOTE: This test assumes that Inputs and Outputs can # successfully be serialized - 'fulfillments': [user_ffill.to_dict()], - 'conditions': [user_cond.to_dict()], + 'inputs': [user_input.to_dict()], + 'outputs': [user_output.to_dict()], 'operation': Transaction.CREATE, 'metadata': None, 'asset': { @@ -355,13 +355,13 @@ def test_tx_serialization_with_incorrect_hash(utx): utx_dict.pop('id') -def test_invalid_fulfillment_initialization(user_ffill, user_pub): - from bigchaindb.common.transaction import Fulfillment +def test_invalid_input_initialization(user_input, user_pub): + from bigchaindb.common.transaction import Input with raises(TypeError): - Fulfillment(user_ffill, user_pub) + Input(user_input, user_pub) with raises(TypeError): - Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') + Input(user_input, tx_input='somethingthatiswrong') def test_transaction_link_serialization(): @@ -370,7 +370,7 @@ def test_transaction_link_serialization(): tx_id = 'a transaction id' expected = { 'txid': tx_id, - 'cid': 0, + 'output': 0, } tx_link = TransactionLink(tx_id, 0) @@ -393,7 +393,7 @@ def test_transaction_link_deserialization(): expected = TransactionLink(tx_id, 0) tx_link = { 'txid': tx_id, - 'cid': 0, + 'output': 0, } tx_link = TransactionLink.from_dict(tx_link) @@ -421,7 +421,7 @@ def test_transaction_link_empty_to_uri(): def test_transaction_link_to_uri(): from bigchaindb.common.transaction import TransactionLink - expected = 'path/transactions/abc/conditions/0' + expected = 'path/transactions/abc/outputs/0' tx_link = TransactionLink('abc', 0).to_uri('path') assert expected == tx_link @@ -437,41 +437,41 @@ def test_cast_transaction_link_to_boolean(): assert bool(TransactionLink(False, False)) is True -def test_add_fulfillment_to_tx(user_ffill, asset_definition): +def test_add_input_to_tx(user_input, asset_definition): from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, asset_definition, [], []) - tx.add_fulfillment(user_ffill) + tx.add_input(user_input) - assert len(tx.fulfillments) == 1 + assert len(tx.inputs) == 1 -def test_add_fulfillment_to_tx_with_invalid_parameters(asset_definition): +def test_add_input_to_tx_with_invalid_parameters(asset_definition): from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, asset_definition) with raises(TypeError): - tx.add_fulfillment('somewronginput') + tx.add_input('somewronginput') -def test_add_condition_to_tx(user_cond, asset_definition): +def test_add_output_to_tx(user_output, asset_definition): from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition) - tx.add_condition(user_cond) + tx.add_output(user_output) - assert len(tx.conditions) == 1 + assert len(tx.outputs) == 1 validate_transaction_model(tx) -def test_add_condition_to_tx_with_invalid_parameters(asset_definition): +def test_add_output_to_tx_with_invalid_parameters(asset_definition): from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, asset_definition, [], []) with raises(TypeError): - tx.add_condition('somewronginput') + tx.add_output('somewronginput') def test_sign_with_invalid_parameters(utx, user_priv): @@ -481,69 +481,67 @@ def test_sign_with_invalid_parameters(utx, user_priv): utx.sign(user_priv) -def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv, +def test_validate_tx_simple_create_signature(user_input, user_output, user_priv, asset_definition): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model - tx = Transaction(Transaction.CREATE, asset_definition, [user_ffill], [user_cond]) - expected = deepcopy(user_cond) + tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output]) + expected = deepcopy(user_output) expected.fulfillment.sign(str(tx).encode(), PrivateKey(user_priv)) tx.sign([user_priv]) - assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + assert tx.inputs[0].to_dict()['fulfillment'] == \ expected.fulfillment.serialize_uri() - assert tx.fulfillments_valid() is True + assert tx.inputs_valid() is True validate_transaction_model(tx) def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, - user_ffill): + user_input): 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, + utx._sign_simple_signature_fulfillment(user_input, 0, 'somemessage', invalid_key_pair) -def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, +def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_input, user3_pub, user3_priv): from bigchaindb.common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): - utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, + utx._sign_threshold_signature_fulfillment(user_user2_threshold_input, 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, + user_user2_threshold_input.owners_before = ['somewrongvalue'] + utx._sign_threshold_signature_fulfillment(user_user2_threshold_input, 0, 'somemessage', None) -def test_validate_fulfillment_with_invalid_parameters(utx): +def test_validate_input_with_invalid_parameters(utx): from bigchaindb.common.transaction import Transaction - input_conditions = [cond.fulfillment.condition_uri for cond - in utx.conditions] + input_conditions = [out.fulfillment.condition_uri for out in utx.outputs] 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 + valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions) + assert not valid -def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv, - asset_definition): +def test_validate_multiple_inputs(user_input, user_output, user_priv, + asset_definition): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey @@ -551,33 +549,33 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv, from .util import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, - [user_ffill, deepcopy(user_ffill)], - [user_cond, deepcopy(user_cond)]) + [user_input, deepcopy(user_input)], + [user_output, deepcopy(user_output)]) expected_first = deepcopy(tx) expected_second = deepcopy(tx) - expected_first.fulfillments = [expected_first.fulfillments[0]] - expected_second.fulfillments = [expected_second.fulfillments[1]] + expected_first.inputs = [expected_first.inputs[0]] + expected_second.inputs = [expected_second.inputs[1]] expected_first_bytes = str(expected_first).encode() - expected_first.fulfillments[0].fulfillment.sign(expected_first_bytes, + expected_first.inputs[0].fulfillment.sign(expected_first_bytes, PrivateKey(user_priv)) expected_second_bytes = str(expected_second).encode() - expected_second.fulfillments[0].fulfillment.sign(expected_second_bytes, - PrivateKey(user_priv)) + expected_second.inputs[0].fulfillment.sign(expected_second_bytes, + PrivateKey(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 + assert tx.inputs[0].to_dict()['fulfillment'] == \ + expected_first.inputs[0].fulfillment.serialize_uri() + assert tx.inputs[1].to_dict()['fulfillment'] == \ + expected_second.inputs[0].fulfillment.serialize_uri() + assert tx.inputs_valid() is True validate_transaction_model(tx) -def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, - user_user2_threshold_cond, +def test_validate_tx_threshold_create_signature(user_user2_threshold_input, + user_user2_threshold_output, user_pub, user2_pub, user_priv, @@ -590,96 +588,90 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, from .util import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, - [user_user2_threshold_ffill], - [user_user2_threshold_cond]) - expected = deepcopy(user_user2_threshold_cond) + [user_user2_threshold_input], + [user_user2_threshold_output]) + expected = deepcopy(user_user2_threshold_output) expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(), PrivateKey(user_priv)) expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(), PrivateKey(user2_priv)) tx.sign([user_priv, user2_priv]) - assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + assert tx.inputs[0].to_dict()['fulfillment'] == \ expected.fulfillment.serialize_uri() - assert tx.fulfillments_valid() is True + assert tx.inputs_valid() is True validate_transaction_model(tx) -def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, - user_priv, user2_pub, - user2_priv, user3_pub, - user3_priv, - asset_definition): +def test_multiple_input_validation_of_transfer_tx(user_input, user_output, + user_priv, user2_pub, + user2_priv, user3_pub, + user3_priv, + asset_definition): from copy import deepcopy from bigchaindb.common.transaction import (Transaction, TransactionLink, - Fulfillment, Condition) + Input, Output) from cryptoconditions import Ed25519Fulfillment from .util import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, - [user_ffill, deepcopy(user_ffill)], - [user_cond, deepcopy(user_cond)]) + [user_input, deepcopy(user_input)], + [user_output, deepcopy(user_output)]) 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', {'id': tx.id}, - fulfillments, conditions) + inputs = [Input(cond.fulfillment, cond.public_keys, + TransactionLink(tx.id, index)) + for index, cond in enumerate(tx.outputs)] + outputs = [Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub]), + Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])] + transfer_tx = Transaction('TRANSFER', {'id': tx.id}, inputs, outputs) transfer_tx = transfer_tx.sign([user_priv]) - assert transfer_tx.fulfillments_valid(tx.conditions) is True + assert transfer_tx.inputs_valid(tx.outputs) is True validate_transaction_model(tx) -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 +def test_validate_inputs_of_transfer_tx_with_invalid_params( + transfer_tx, cond_uri, utx, user2_pub, user_priv): + from bigchaindb.common.transaction import Output 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 + invalid_out = Output(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid']) + assert transfer_tx.inputs_valid([invalid_out]) is False + invalid_out = utx.outputs[0] + invalid_out.public_key = 'invalid' + assert transfer_tx.inputs_valid([invalid_out]) is True with raises(TypeError): - assert transfer_tx.fulfillments_valid(None) is False + assert transfer_tx.inputs_valid(None) is False with raises(AttributeError): - transfer_tx.fulfillments_valid('not a list') + transfer_tx.inputs_valid('not a list') with raises(ValueError): - transfer_tx.fulfillments_valid([]) + transfer_tx.inputs_valid([]) with raises(TypeError): transfer_tx.operation = "Operation that doesn't exist" - transfer_tx.fulfillments_valid([utx.conditions[0]]) + transfer_tx.inputs_valid([utx.outputs[0]]) -def test_create_create_transaction_single_io(user_cond, user_pub, data): +def test_create_create_transaction_single_io(user_output, user_pub, data): from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model expected = { - 'conditions': [user_cond.to_dict()], + 'outputs': [user_output.to_dict()], 'metadata': data, 'asset': { 'data': data, }, - 'fulfillments': [ + 'inputs': [ { 'owners_before': [ user_pub ], 'fulfillment': None, - 'input': None + 'fulfills': None } ], 'operation': 'CREATE', @@ -689,7 +681,7 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data): tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data, asset=data) tx_dict = tx.to_dict() - tx_dict['fulfillments'][0]['fulfillment'] = None + tx_dict['inputs'][0]['fulfillment'] = None tx_dict.pop('id') assert tx_dict == expected @@ -703,23 +695,23 @@ def test_validate_single_io_create_transaction(user_pub, user_priv, data, tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data) tx = tx.sign([user_priv]) - assert tx.fulfillments_valid() is True + assert tx.inputs_valid() is True -def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, +def test_create_create_transaction_multiple_io(user_output, user2_output, user_pub, user2_pub, asset_definition): - from bigchaindb.common.transaction import Transaction, Fulfillment + from bigchaindb.common.transaction import Transaction, Input # a fulfillment for a create transaction with multiple `owners_before` # is a fulfillment for an implicit threshold condition with # weight = len(owners_before) - ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict() + input = Input.generate([user_pub, user2_pub]).to_dict() expected = { - 'conditions': [user_cond.to_dict(), user2_cond.to_dict()], + 'outputs': [user_output.to_dict(), user2_output.to_dict()], 'metadata': { 'message': 'hello' }, - 'fulfillments': [ffill], + 'inputs': [input], 'operation': 'CREATE', 'version': 1 } @@ -742,29 +734,29 @@ def test_validate_multiple_io_create_transaction(user_pub, user_priv, [([user_pub], 1), ([user2_pub], 1)], metadata={'message': 'hello'}) tx = tx.sign([user_priv, user2_priv]) - assert tx.fulfillments_valid() is True + assert tx.inputs_valid() is True validate_transaction_model(tx) def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, - user_user2_threshold_cond, - user_user2_threshold_ffill, data): + user_user2_threshold_output, + user_user2_threshold_input, data): from bigchaindb.common.transaction import Transaction expected = { - 'conditions': [user_user2_threshold_cond.to_dict()], + 'outputs': [user_user2_threshold_output.to_dict()], 'metadata': data, 'asset': { 'data': data, }, - 'fulfillments': [ + 'inputs': [ { 'owners_before': [ user_pub, ], 'fulfillment': None, - 'input': None + 'fulfills': None, }, ], 'operation': 'CREATE', @@ -774,7 +766,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, metadata=data, asset=data) tx_dict = tx.to_dict() tx_dict.pop('id') - tx_dict['fulfillments'][0]['fulfillment'] = None + tx_dict['inputs'][0]['fulfillment'] = None assert tx_dict == expected @@ -787,7 +779,7 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], metadata=data) tx = tx.sign([user_priv]) - assert tx.fulfillments_valid() is True + assert tx.inputs_valid() is True validate_transaction_model(tx) @@ -816,18 +808,18 @@ def test_create_create_transaction_with_invalid_parameters(user_pub): asset='not a dict or none') -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_outputs_to_inputs(tx): + inputs = tx.to_inputs([0]) + assert len(inputs) == 1 + input = inputs.pop() + assert input.owners_before == tx.outputs[0].public_keys + assert input.fulfillment == tx.outputs[0].fulfillment + assert input.fulfills.txid == tx.id + assert input.fulfills.output == 0 def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, - user2_cond, user_priv): + user2_output, user_priv): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.transaction import Transaction @@ -835,20 +827,20 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, from .util import validate_transaction_model expected = { - 'conditions': [user2_cond.to_dict()], + 'outputs': [user2_output.to_dict()], 'metadata': None, 'asset': { 'id': tx.id, }, - 'fulfillments': [ + 'inputs': [ { 'owners_before': [ user_pub ], 'fulfillment': None, - 'input': { + 'fulfills': { 'txid': tx.id, - 'cid': 0 + 'output': 0 } } ], @@ -866,19 +858,19 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, expected_input.fulfillment.sign(serialize(expected).encode(), PrivateKey(user_priv)) expected_ffill = expected_input.fulfillment.serialize_uri() - transfer_ffill = transfer_tx['fulfillments'][0]['fulfillment'] + transfer_ffill = transfer_tx['inputs'][0]['fulfillment'] assert transfer_ffill == expected_ffill transfer_tx = Transaction.from_dict(transfer_tx) - assert transfer_tx.fulfillments_valid([tx.conditions[0]]) is True + assert transfer_tx.inputs_valid([tx.outputs[0]]) is True validate_transaction_model(transfer_tx) def test_create_transfer_transaction_multiple_io(user_pub, user_priv, user2_pub, user2_priv, - user3_pub, user2_cond, + user3_pub, user2_output, asset_definition): from bigchaindb.common.transaction import Transaction @@ -887,26 +879,26 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, tx = tx.sign([user_priv]) expected = { - 'conditions': [user2_cond.to_dict(), user2_cond.to_dict()], + 'outputs': [user2_output.to_dict(), user2_output.to_dict()], 'metadata': None, - 'fulfillments': [ + 'inputs': [ { 'owners_before': [ user_pub ], 'fulfillment': None, - 'input': { + 'fulfills': { 'txid': tx.id, - 'cid': 0 + 'output': 0 } }, { 'owners_before': [ user2_pub ], 'fulfillment': None, - 'input': { + 'fulfills': { 'txid': tx.id, - 'cid': 1 + 'output': 1 } } ], @@ -919,14 +911,14 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, asset_id=tx.id) transfer_tx = transfer_tx.sign([user_priv, user2_priv]) - assert len(transfer_tx.fulfillments) == 2 - assert len(transfer_tx.conditions) == 2 + assert len(transfer_tx.inputs) == 2 + assert len(transfer_tx.outputs) == 2 - assert transfer_tx.fulfillments_valid(tx.conditions) is True + assert transfer_tx.inputs_valid(tx.outputs) is True transfer_tx = transfer_tx.to_dict() - transfer_tx['fulfillments'][0]['fulfillment'] = None - transfer_tx['fulfillments'][1]['fulfillment'] = None + transfer_tx['inputs'][0]['fulfillment'] = None + transfer_tx['inputs'][1]['fulfillment'] = None transfer_tx.pop('asset') transfer_tx.pop('id') @@ -956,17 +948,17 @@ def test_create_transfer_with_invalid_parameters(tx, user_pub): ['not a string']) -def test_cant_add_empty_condition(): +def test_cant_add_empty_output(): from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, None) with raises(TypeError): - tx.add_condition(None) + tx.add_output(None) -def test_cant_add_empty_fulfillment(): +def test_cant_add_empty_input(): from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, None) with raises(TypeError): - tx.add_fulfillment(None) + tx.add_input(None) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index e294d490..bc48615f 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -279,7 +279,7 @@ class TestBigchainApi(object): assert len(block['block']['transactions']) == 1 assert block['block']['transactions'][0]['operation'] == 'GENESIS' - assert block['block']['transactions'][0]['fulfillments'][0]['input'] is None + assert block['block']['transactions'][0]['inputs'][0]['fulfills'] is None @pytest.mark.genesis def test_create_genesis_block_fails_if_table_not_empty(self, b): @@ -532,15 +532,15 @@ class TestBigchainApi(object): def test_non_create_input_not_found(self, b, user_pk): from cryptoconditions import Ed25519Fulfillment from bigchaindb.common.exceptions import TransactionDoesNotExist - from bigchaindb.common.transaction import Fulfillment, TransactionLink + from bigchaindb.common.transaction import Input, TransactionLink from bigchaindb.models import Transaction from bigchaindb import Bigchain - # Create a fulfillment for a non existing transaction - fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_pk), - [user_pk], - TransactionLink('somethingsomething', 0)) - tx = Transaction.transfer([fulfillment], [([user_pk], 1)], + # Create an input for a non existing transaction + input = Input(Ed25519Fulfillment(public_key=user_pk), + [user_pk], + TransactionLink('somethingsomething', 0)) + tx = Transaction.transfer([input], [([user_pk], 1)], asset_id='mock_asset_link') with pytest.raises(TransactionDoesNotExist): @@ -563,16 +563,16 @@ class TestTransactionValidation(object): def test_create_operation_with_inputs(self, b, user_pk, create_tx): from bigchaindb.common.transaction import TransactionLink - # Manipulate fulfillment so that it has a `tx_input` defined even + # Manipulate input so that it has a `fulfills` defined even # though it shouldn't have one - create_tx.fulfillments[0].tx_input = TransactionLink('abc', 0) + create_tx.inputs[0].fulfills = TransactionLink('abc', 0) with pytest.raises(ValueError) as excinfo: b.validate_transaction(create_tx) assert excinfo.value.args[0] == 'A CREATE operation has no inputs' def test_transfer_operation_no_inputs(self, b, user_pk, signed_transfer_tx): - signed_transfer_tx.fulfillments[0].tx_input = None + signed_transfer_tx.inputs[0].fulfills = None with pytest.raises(ValueError) as excinfo: b.validate_transaction(signed_transfer_tx) @@ -582,7 +582,7 @@ class TestTransactionValidation(object): from bigchaindb.common.exceptions import TransactionDoesNotExist from bigchaindb.common.transaction import TransactionLink - signed_transfer_tx.fulfillments[0].tx_input = TransactionLink('c', 0) + signed_transfer_tx.inputs[0].fulfills = TransactionLink('c', 0) with pytest.raises(TransactionDoesNotExist): b.validate_transaction(signed_transfer_tx) @@ -598,7 +598,7 @@ class TestTransactionValidation(object): tx = Transaction.create([pk], [([user_pk], 1)]) tx.operation = 'TRANSFER' tx.asset = {'id': input_transaction.id} - tx.fulfillments[0].tx_input = input_tx + tx.inputs[0].fulfills = input_tx with pytest.raises(InvalidSignature): b.validate_transaction(tx) @@ -781,8 +781,8 @@ class TestMultipleInputs(object): # validate transaction assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == 1 - assert len(tx.conditions) == 1 + assert len(tx.inputs) == 1 + assert len(tx.outputs) == 1 def test_single_owner_before_multiple_owners_after_single_input(self, b, user_sk, @@ -803,8 +803,8 @@ class TestMultipleInputs(object): tx = tx.sign([user_sk]) assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == 1 - assert len(tx.conditions) == 1 + assert len(tx.inputs) == 1 + assert len(tx.outputs) == 1 @pytest.mark.usefixtures('inputs') def test_multiple_owners_before_single_owner_after_single_input(self, b, @@ -835,8 +835,8 @@ class TestMultipleInputs(object): # validate transaction assert b.is_valid_transaction(transfer_tx) == transfer_tx - assert len(transfer_tx.fulfillments) == 1 - assert len(transfer_tx.conditions) == 1 + assert len(transfer_tx.inputs) == 1 + assert len(transfer_tx.outputs) == 1 @pytest.mark.usefixtures('inputs') def test_multiple_owners_before_multiple_owners_after_single_input(self, b, @@ -868,8 +868,8 @@ class TestMultipleInputs(object): tx = tx.sign([user_sk, user2_sk]) assert b.is_valid_transaction(tx) == tx - assert len(tx.fulfillments) == 1 - assert len(tx.conditions) == 1 + assert len(tx.inputs) == 1 + assert len(tx.outputs) == 1 def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk): from bigchaindb.common import crypto @@ -1025,8 +1025,8 @@ class TestMultipleInputs(object): # check spents input_txid = owned_inputs_user1.txid - input_cid = owned_inputs_user1.cid - spent_inputs_user1 = b.get_spent(input_txid, input_cid) + input_idx = owned_inputs_user1.output + spent_inputs_user1 = b.get_spent(input_txid, input_idx) assert spent_inputs_user1 is None # create a transaction and block @@ -1036,7 +1036,7 @@ class TestMultipleInputs(object): block = b.create_block([tx]) b.write_block(block) - spent_inputs_user1 = b.get_spent(input_txid, input_cid) + spent_inputs_user1 = b.get_spent(input_txid, input_idx) assert spent_inputs_user1 == tx def test_get_spent_single_tx_single_output_invalid_block(self, b, @@ -1062,8 +1062,8 @@ class TestMultipleInputs(object): # check spents input_txid = owned_inputs_user1.txid - input_cid = owned_inputs_user1.cid - spent_inputs_user1 = b.get_spent(input_txid, input_cid) + input_idx = owned_inputs_user1.output + spent_inputs_user1 = b.get_spent(input_txid, input_idx) assert spent_inputs_user1 is None # create a transaction and block @@ -1078,7 +1078,7 @@ class TestMultipleInputs(object): b.write_vote(vote) # NOTE: I have no idea why this line is here b.get_transaction(tx.id) - spent_inputs_user1 = b.get_spent(input_txid, input_cid) + spent_inputs_user1 = b.get_spent(input_txid, input_idx) # Now there should be no spents (the block is invalid) assert spent_inputs_user1 is None @@ -1103,7 +1103,7 @@ class TestMultipleInputs(object): # check spents for input_tx in owned_inputs_user1: - assert b.get_spent(input_tx.txid, input_tx.cid) is None + assert b.get_spent(input_tx.txid, input_tx.output) is None # transfer the first 2 inputs tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2], @@ -1115,12 +1115,12 @@ class TestMultipleInputs(object): # check that used inputs are marked as spent for ffill in tx_create.to_inputs()[:2]: - spent_tx = b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) + spent_tx = b.get_spent(ffill.fulfills.txid, ffill.fulfills.output) assert spent_tx == tx_transfer_signed # check if remaining transaction that was unspent is also perceived # spendable by BigchainDB - assert b.get_spent(tx_create.to_inputs()[2].tx_input.txid, 2) is None + assert b.get_spent(tx_create.to_inputs()[2].fulfills.txid, 2) is None def test_get_spent_multiple_owners(self, b, user_sk, user_pk): from bigchaindb.common import crypto @@ -1143,7 +1143,7 @@ class TestMultipleInputs(object): # check spents for input_tx in owned_inputs_user1: - assert b.get_spent(input_tx.txid, input_tx.cid) is None + assert b.get_spent(input_tx.txid, input_tx.output) is None # create a transaction tx = Transaction.transfer(transactions[0].to_inputs(), diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index 8eae0755..2983b1db 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -154,7 +154,7 @@ def test_vote_accumulates_transactions(b): validation = vote_obj.validate_tx(tx, 123, 1) assert validation == (True, 123, 1) - tx.fulfillments[0].fulfillment.signature = None + tx.inputs[0].fulfillment.signature = None validation = vote_obj.validate_tx(tx, 456, 10) assert validation == (False, 456, 10) diff --git a/tests/test_models.py b/tests/test_models.py index f462e3fe..554f3436 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -12,7 +12,7 @@ class TestTransactionModel(object): tx.validate(b) tx.operation = 'CREATE' - tx.fulfillments = [] + tx.inputs = [] with raises(ValueError): tx.validate(b) diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index 304dcce2..fc1d02ac 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -37,8 +37,8 @@ def test_post_create_transaction_endpoint(b, client): tx = tx.sign([user_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) - assert res.json['fulfillments'][0]['owners_before'][0] == user_pub - assert res.json['conditions'][0]['owners_after'][0] == user_pub + assert res.json['inputs'][0]['owners_before'][0] == user_pub + assert res.json['outputs'][0]['public_keys'][0] == user_pub def test_post_create_transaction_with_invalid_id(b, client): @@ -65,7 +65,7 @@ def test_post_create_transaction_with_invalid_signature(b, client): tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]).to_dict() - tx['fulfillments'][0]['fulfillment'] = 'cf:0:0' + tx['inputs'][0]['fulfillment'] = 'cf:0:0' res = client.post(TX_ENDPOINT, data=json.dumps(tx)) assert res.status_code == 400 @@ -139,8 +139,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk): res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) - assert res.json['fulfillments'][0]['owners_before'][0] == user_pk - assert res.json['conditions'][0]['owners_after'][0] == user_pub + assert res.json['inputs'][0]['owners_before'][0] == user_pk + assert res.json['outputs'][0]['public_keys'][0] == user_pub @pytest.mark.bdb