Extra renames and small fixes for inputs-outputs (#952)

* Fix typos pointed out in review by @ttmc

* Reword description of an input in the transaction schema

* Re-add removed comment in transaction model

* Fix small typos in some comments in the transaction model

* Add trailling commas to a multiline dict in the transaction model tests

* Fix small things with server docs changes

* Add description of public keys' association with conditions in transaction concept docs

* Reword description of Transaction.create's  and  args

* Rename output_uri to output_condition_uri

* Fix hardcoded class name vs. self.__class__

* Rename instances of 'out' with 'output'

* Rename instances of  and  with  to avoid name clash with built-in

* Remove unnecessary renaming of cryptoconditions.Fulfillment import in transaction model

* Remove instances of  in transaction model

* Remove usages of fulfillment in cases where input makes more sense

* Reword docstrings for init methods in transaction models

* Rename usages of condition where output is now a better fit

* Add descriptions to TransactionLink's txid and idx in schema

* Minor correction to output idx description in transaction yaml
This commit is contained in:
Brett Sun 2016-12-14 16:39:35 +01:00 committed by Scott Sadler
parent 3d495a8987
commit 2f4da6a32f
8 changed files with 126 additions and 112 deletions

View File

@ -133,8 +133,8 @@ definitions:
output: output:
type: object type: object
description: | description: |
A transaction output. Describes a quantity of an asset and a locking A transaction output. Describes the quantity of an asset and the
script to spend the output. requirements that must be met to spend the output.
See also: Input_. See also: Input_.
additionalProperties: false additionalProperties: false
@ -173,8 +173,8 @@ definitions:
input: input:
type: "object" type: "object"
description: description:
An input spends a previous output, by providing a fulfillment to the output's An input spends a previous output, by providing one or more fulfillments
condition. that fulfill the conditions of the previous output.
additionalProperties: false additionalProperties: false
required: required:
- owners_before - owners_before
@ -219,8 +219,12 @@ definitions:
properties: properties:
idx: idx:
"$ref": "#/definitions/offset" "$ref": "#/definitions/offset"
description: |
Index of the output containing the condition being fulfilled
txid: txid:
"$ref": "#/definitions/sha3_hexdigest" "$ref": "#/definitions/sha3_hexdigest"
description: |
Transaction ID containing the output to spend
- type: 'null' - type: 'null'
metadata: metadata:
anyOf: anyOf:

View File

@ -2,8 +2,8 @@ from copy import deepcopy
from functools import reduce from functools import reduce
from uuid import uuid4 from uuid import uuid4
from cryptoconditions import (Fulfillment as CCFulfillment, from cryptoconditions import (Fulfillment, ThresholdSha256Fulfillment,
ThresholdSha256Fulfillment, Ed25519Fulfillment) Ed25519Fulfillment)
from cryptoconditions.exceptions import ParsingError from cryptoconditions.exceptions import ParsingError
from bigchaindb.common.crypto import PrivateKey, hash_data from bigchaindb.common.crypto import PrivateKey, hash_data
@ -16,6 +16,8 @@ from bigchaindb.common.util import serialize, gen_timestamp
class Input(object): class Input(object):
"""A Input is used to spend assets locked by an Output. """A Input is used to spend assets locked by an Output.
Wraps around a Crypto-condition Fulfillment.
Attributes: Attributes:
fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment
to be signed with a private key. to be signed with a private key.
@ -27,7 +29,7 @@ class Input(object):
""" """
def __init__(self, fulfillment, owners_before, fulfills=None): def __init__(self, fulfillment, owners_before, fulfills=None):
"""Fulfillment shims a Cryptocondition Fulfillment for BigchainDB. """Create an instance of an :class:`~.Input`.
Args: Args:
fulfillment (:class:`cryptoconditions.Fulfillment`): A fulfillment (:class:`cryptoconditions.Fulfillment`): A
@ -80,12 +82,12 @@ class Input(object):
except AttributeError: except AttributeError:
fulfills = None fulfills = None
input = { input_ = {
'owners_before': self.owners_before, 'owners_before': self.owners_before,
'fulfills': fulfills, 'fulfills': fulfills,
'fulfillment': fulfillment, 'fulfillment': fulfillment,
} }
return input return input_
@classmethod @classmethod
def generate(cls, public_keys): def generate(cls, public_keys):
@ -104,7 +106,7 @@ class Input(object):
Fulfillment that is not yet signed. Fulfillment that is not yet signed.
Args: Args:
data (dict): The Fulfillment to be transformed. data (dict): The Input to be transformed.
Returns: Returns:
:class:`~bigchaindb.common.transaction.Input` :class:`~bigchaindb.common.transaction.Input`
@ -113,20 +115,20 @@ class Input(object):
InvalidSignature: If an Input's URI couldn't be parsed. InvalidSignature: If an Input's URI couldn't be parsed.
""" """
try: try:
fulfillment = CCFulfillment.from_uri(data['fulfillment']) fulfillment = Fulfillment.from_uri(data['fulfillment'])
except ValueError: except ValueError:
# TODO FOR CC: Throw an `InvalidSignature` error in this case. # TODO FOR CC: Throw an `InvalidSignature` error in this case.
raise InvalidSignature("Fulfillment URI couldn't been parsed") raise InvalidSignature("Fulfillment URI couldn't been parsed")
except TypeError: except TypeError:
# NOTE: See comment about this special case in # NOTE: See comment about this special case in
# `Input.to_dict` # `Input.to_dict`
fulfillment = CCFulfillment.from_dict(data['fulfillment']) fulfillment = Fulfillment.from_dict(data['fulfillment'])
fulfills = TransactionLink.from_dict(data['fulfills']) fulfills = TransactionLink.from_dict(data['fulfills'])
return cls(fulfillment, data['owners_before'], fulfills) return cls(fulfillment, data['owners_before'], fulfills)
class TransactionLink(object): class TransactionLink(object):
"""An object for unidirectional linking to a Transaction's Condition. """An object for unidirectional linking to a Transaction's Output.
Attributes: Attributes:
txid (str, optional): A Transaction to link to. txid (str, optional): A Transaction to link to.
@ -135,7 +137,7 @@ class TransactionLink(object):
""" """
def __init__(self, txid=None, idx=None): def __init__(self, txid=None, idx=None):
"""Used to point to a specific Condition of a Transaction. """Create an instance of a :class:`~.TransactionLink`.
Note: Note:
In an IPLD implementation, this class is not necessary anymore, In an IPLD implementation, this class is not necessary anymore,
@ -198,15 +200,17 @@ class TransactionLink(object):
class Output(object): class Output(object):
"""An Output is used to lock an asset. """An Output is used to lock an asset.
Wraps around a Crypto-condition Condition.
Attributes: Attributes:
fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment
to extract a Condition from. 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. owners before a Transaction was confirmed.
""" """
def __init__(self, fulfillment, public_keys=None, amount=1): def __init__(self, fulfillment, public_keys=None, amount=1):
"""Condition shims a Cryptocondition condition for BigchainDB. """Create an instance of a :class:`~.Output`.
Args: Args:
fulfillment (:class:`cryptoconditions.Fulfillment`): A fulfillment (:class:`cryptoconditions.Fulfillment`): A
@ -214,7 +218,7 @@ class Output(object):
public_keys (: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. owners before a Transaction was confirmed.
amount (int): The amount of Assets to be locked with this amount (int): The amount of Assets to be locked with this
Condition. Output.
Raises: Raises:
TypeError: if `public_keys` is not instance of `list`. TypeError: if `public_keys` is not instance of `list`.
@ -263,7 +267,7 @@ class Output(object):
@classmethod @classmethod
def generate(cls, public_keys, amount): def generate(cls, public_keys, amount):
"""Generates a Condition from a specifically formed tuple or list. """Generates a Output from a specifically formed tuple or list.
Note: Note:
If a ThresholdCondition has to be generated where the threshold If a ThresholdCondition has to be generated where the threshold
@ -276,10 +280,10 @@ class Output(object):
public_keys (: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 the users that should be able to fulfill the Condition
that is being created. that is being created.
amount (:obj:`int`): The amount locked by the condition. amount (:obj:`int`): The amount locked by the Output.
Returns: Returns:
A Condition that can be used in a Transaction. An Output that can be used in a Transaction.
Raises: Raises:
TypeError: If `public_keys` is not an instance of `list`. TypeError: If `public_keys` is not an instance of `list`.
@ -308,49 +312,48 @@ class Output(object):
return cls(threshold_cond, public_keys, amount=amount) return cls(threshold_cond, public_keys, amount=amount)
@classmethod @classmethod
def _gen_condition(cls, initial, current): def _gen_condition(cls, initial, new_public_keys):
"""Generates ThresholdSha256 conditions from a list of new owners. """Generates ThresholdSha256 conditions from a list of new owners.
Note: Note:
This method is intended only to be used with a reduce function. This method is intended only to be used with a reduce function.
For a description on how to use this method, see For a description on how to use this method, see
`Condition.generate`. :meth:`~.Output.generate`.
Args: Args:
initial (:class:`cryptoconditions.ThresholdSha256Fulfillment`): initial (:class:`cryptoconditions.ThresholdSha256Fulfillment`):
A Condition representing the overall root. A Condition representing the overall root.
current (:obj:`list` of :obj:`str`|str): A list of new owners new_public_keys (:obj:`list` of :obj:`str`|str): A list of new
or a single new owner. owners or a single new owner.
Returns: Returns:
:class:`cryptoconditions.ThresholdSha256Fulfillment`: :class:`cryptoconditions.ThresholdSha256Fulfillment`:
""" """
owners_after = current
try: try:
threshold = len(owners_after) threshold = len(new_public_keys)
except TypeError: except TypeError:
threshold = None 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) ffill = ThresholdSha256Fulfillment(threshold=threshold)
reduce(cls._gen_condition, owners_after, ffill) reduce(cls._gen_condition, new_public_keys, ffill)
elif isinstance(owners_after, list) and len(owners_after) <= 1: elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1:
raise ValueError('Sublist cannot contain single owner') raise ValueError('Sublist cannot contain single owner')
else: else:
try: try:
owners_after = owners_after.pop() new_public_keys = new_public_keys.pop()
except AttributeError: except AttributeError:
pass pass
try: try:
ffill = Ed25519Fulfillment(public_key=owners_after) ffill = Ed25519Fulfillment(public_key=new_public_keys)
except TypeError: except TypeError:
# NOTE: Instead of submitting base58 encoded addresses, a user # NOTE: Instead of submitting base58 encoded addresses, a user
# of this class can also submit fully instantiated # of this class can also submit fully instantiated
# Cryptoconditions. In the case of casting `owners_after` # Cryptoconditions. In the case of casting
# to a Ed25519Fulfillment with the result of a # `new_public_keys` to a Ed25519Fulfillment with the
# `TypeError`, we're assuming that `owners_after` is a # result of a `TypeError`, we're assuming that
# Cryptocondition then. # `new_public_keys` is a Cryptocondition then.
ffill = owners_after ffill = new_public_keys
initial.add_subfulfillment(ffill) initial.add_subfulfillment(ffill)
return initial return initial
@ -371,7 +374,7 @@ class Output(object):
:class:`~bigchaindb.common.transaction.Output` :class:`~bigchaindb.common.transaction.Output`
""" """
try: try:
fulfillment = CCFulfillment.from_dict(data['condition']['details']) fulfillment = Fulfillment.from_dict(data['condition']['details'])
except KeyError: except KeyError:
# NOTE: Hashlock condition case # NOTE: Hashlock condition case
fulfillment = data['condition']['uri'] fulfillment = data['condition']['uri']
@ -518,7 +521,7 @@ class AssetLink(Asset):
""" """
def __init__(self, data_id=None): def __init__(self, data_id=None):
"""Used to point to a specific Asset. """Create an instance of a :class:`~.AssetLink`.
Args: Args:
data_id (str): A Asset to link to. data_id (str): A Asset to link to.
@ -598,10 +601,10 @@ class Transaction(object):
asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset
to be transferred or created in a Transaction. to be transferred or created in a Transaction.
inputs (:obj:`list` of :class:`~bigchaindb.common. inputs (:obj:`list` of :class:`~bigchaindb.common.
transaction.Fulfillment`, optional): Define the assets to transaction.Input`, optional): Define the assets to
spend. spend.
outputs (:obj:`list` of :class:`~bigchaindb.common. outputs (:obj:`list` of :class:`~bigchaindb.common.
transaction.Condition`, optional): Define the assets to transaction.Output`, optional): Define the assets to
lock. lock.
metadata (dict): metadata (dict):
Metadata to be stored along with the Transaction. Metadata to be stored along with the Transaction.
@ -640,7 +643,7 @@ class Transaction(object):
# for transactions other then CREATE we only have an id so there is # for transactions other then CREATE we only have an id so there is
# nothing we can validate # nothing we can validate
if self.operation == self.CREATE: if self.operation == self.CREATE:
amount = sum([condition.amount for condition in self.outputs]) amount = sum([output.amount for output in self.outputs])
self.asset.validate_asset(amount=amount) self.asset.validate_asset(amount=amount)
@classmethod @classmethod
@ -659,9 +662,11 @@ class Transaction(object):
Args: Args:
creators (:obj:`list` of :obj:`str`): A list of keys that creators (:obj:`list` of :obj:`str`): A list of keys that
represent the creators of this Transaction. represent the creators of the asset created by this
Transaction.
recipients (:obj:`list` of :obj:`str`): A list of keys that recipients (:obj:`list` of :obj:`str`): A list of keys that
represent the recipients of this Transaction. represent the recipients of the asset created by this
Transaction.
metadata (dict): Python dictionary to be stored along with the metadata (dict): Python dictionary to be stored along with the
Transaction. Transaction.
asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset
@ -783,6 +788,8 @@ class Transaction(object):
:obj:`list` of :class:`~bigchaindb.common.transaction. :obj:`list` of :class:`~bigchaindb.common.transaction.
Input` Input`
""" """
# NOTE: If no indices are passed, we just assume to take all outputs
# as inputs.
indices = indices or range(len(self.outputs)) indices = indices or range(len(self.outputs))
return [ return [
Input(self.outputs[idx].fulfillment, Input(self.outputs[idx].fulfillment,
@ -791,16 +798,16 @@ class Transaction(object):
for idx in indices for idx in indices
] ]
def add_input(self, input): def add_input(self, input_):
"""Adds an input to a Transaction's list of inputs. """Adds an input to a Transaction's list of inputs.
Args: Args:
fulfillment (:class:`~bigchaindb.common.transaction. input_ (:class:`~bigchaindb.common.transaction.
Input`): An Input to be added to the Transaction. Input`): An Input to be added to the Transaction.
""" """
if not isinstance(input, Input): if not isinstance(input_, Input):
raise TypeError('`input` must be a Input instance') raise TypeError('`input_` must be a Input instance')
self.inputs.append(input) self.inputs.append(input_)
def add_output(self, output): def add_output(self, output):
"""Adds an output to a Transaction's list of outputs. """Adds an output to a Transaction's list of outputs.
@ -857,21 +864,21 @@ class Transaction(object):
key_pairs = {gen_public_key(PrivateKey(private_key)): key_pairs = {gen_public_key(PrivateKey(private_key)):
PrivateKey(private_key) for private_key in private_keys} PrivateKey(private_key) for private_key in private_keys}
for index, input in enumerate(self.inputs): for index, input_ in enumerate(self.inputs):
# NOTE: We clone the current transaction but only add the output # NOTE: We clone the current transaction but only add the output
# and input we're currently working on plus all # and input we're currently working on plus all
# previously signed ones. # previously signed ones.
tx_partial = Transaction(self.operation, self.asset, [input], tx_partial = Transaction(self.operation, self.asset, [input_],
self.outputs, self.metadata, self.outputs, self.metadata,
self.version) self.version)
tx_partial_dict = tx_partial.to_dict() tx_partial_dict = tx_partial.to_dict()
tx_partial_dict = Transaction._remove_signatures(tx_partial_dict) tx_partial_dict = Transaction._remove_signatures(tx_partial_dict)
tx_serialized = Transaction._to_str(tx_partial_dict) tx_serialized = Transaction._to_str(tx_partial_dict)
self._sign_input(input, index, tx_serialized, key_pairs) self._sign_input(input_, index, tx_serialized, key_pairs)
return self return self
def _sign_input(self, input, index, tx_serialized, key_pairs): def _sign_input(self, input_, index, tx_serialized, key_pairs):
"""Signs a single Input with a partial Transaction as message. """Signs a single Input with a partial Transaction as message.
Note: Note:
@ -881,29 +888,29 @@ class Transaction(object):
- ThresholdSha256Fulfillment. - ThresholdSha256Fulfillment.
Args: Args:
input (:class:`~bigchaindb.common.transaction. input_ (:class:`~bigchaindb.common.transaction.
Input`) The Input to be signed. Input`) The Input to be signed.
index (int): The index of 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. tx_serialized (str): The Transaction to be used as message.
key_pairs (dict): The keys to sign the Transaction with. key_pairs (dict): The keys to sign the Transaction with.
""" """
if isinstance(input.fulfillment, Ed25519Fulfillment): if isinstance(input_.fulfillment, Ed25519Fulfillment):
self._sign_simple_signature_fulfillment(input, index, self._sign_simple_signature_fulfillment(input_, index,
tx_serialized, key_pairs) tx_serialized, key_pairs)
elif isinstance(input.fulfillment, ThresholdSha256Fulfillment): elif isinstance(input_.fulfillment, ThresholdSha256Fulfillment):
self._sign_threshold_signature_fulfillment(input, index, self._sign_threshold_signature_fulfillment(input_, index,
tx_serialized, tx_serialized,
key_pairs) key_pairs)
else: else:
raise ValueError("Fulfillment couldn't be matched to " raise ValueError("Fulfillment couldn't be matched to "
'Cryptocondition fulfillment type.') 'Cryptocondition fulfillment type.')
def _sign_simple_signature_fulfillment(self, input, index, def _sign_simple_signature_fulfillment(self, input_, index,
tx_serialized, key_pairs): tx_serialized, key_pairs):
"""Signs a Ed25519Fulfillment. """Signs a Ed25519Fulfillment.
Args: Args:
input (:class:`~bigchaindb.common.transaction. input_ (:class:`~bigchaindb.common.transaction.
Input`) The input to be signed. Input`) The input to be signed.
index (int): The index of the input to be index (int): The index of the input to be
signed. signed.
@ -911,35 +918,35 @@ class Transaction(object):
key_pairs (dict): The keys to sign the Transaction with. key_pairs (dict): The keys to sign the Transaction with.
""" """
# NOTE: To eliminate the dangers of accidentally signing a condition by # NOTE: To eliminate the dangers of accidentally signing a condition by
# reference, we remove the reference of input here # reference, we remove the reference of input_ here
# intentionally. If the user of this class knows how to use it, # intentionally. If the user of this class knows how to use it,
# this should never happen, but then again, never say never. # this should never happen, but then again, never say never.
input = deepcopy(input) input_ = deepcopy(input_)
public_key = input.owners_before[0] public_key = input_.owners_before[0]
try: try:
# cryptoconditions makes no assumptions of the encoding of the # cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings # message to sign or verify. It only accepts bytestrings
input.fulfillment.sign(tx_serialized.encode(), key_pairs[public_key]) input_.fulfillment.sign(tx_serialized.encode(), key_pairs[public_key])
except KeyError: except KeyError:
raise KeypairMismatchException('Public key {} is not a pair to ' raise KeypairMismatchException('Public key {} is not a pair to '
'any of the private keys' 'any of the private keys'
.format(public_key)) .format(public_key))
self.inputs[index] = input self.inputs[index] = input_
def _sign_threshold_signature_fulfillment(self, input, index, def _sign_threshold_signature_fulfillment(self, input_, index,
tx_serialized, key_pairs): tx_serialized, key_pairs):
"""Signs a ThresholdSha256Fulfillment. """Signs a ThresholdSha256Fulfillment.
Args: Args:
input (:class:`~bigchaindb.common.transaction. input_ (:class:`~bigchaindb.common.transaction.
Input`) The Input to be signed. Input`) The Input to be signed.
index (int): The index of the Input to be index (int): The index of the Input to be
signed. signed.
tx_serialized (str): The Transaction to be used as message. tx_serialized (str): The Transaction to be used as message.
key_pairs (dict): The keys to sign the Transaction with. key_pairs (dict): The keys to sign the Transaction with.
""" """
input = deepcopy(input) input_ = deepcopy(input_)
for owner_before in input.owners_before: for owner_before in input_.owners_before:
try: try:
# TODO: CC should throw a KeypairMismatchException, instead of # TODO: CC should throw a KeypairMismatchException, instead of
# our manual mapping here # our manual mapping here
@ -950,7 +957,7 @@ class Transaction(object):
# TODO FOR CC: `get_subcondition` is singular. One would not # TODO FOR CC: `get_subcondition` is singular. One would not
# expect to get a list back. # expect to get a list back.
ccffill = input.fulfillment ccffill = input_.fulfillment
subffill = ccffill.get_subcondition_from_vk(owner_before)[0] subffill = ccffill.get_subcondition_from_vk(owner_before)[0]
except IndexError: except IndexError:
raise KeypairMismatchException('Public key {} cannot be found ' raise KeypairMismatchException('Public key {} cannot be found '
@ -966,7 +973,7 @@ class Transaction(object):
# cryptoconditions makes no assumptions of the encoding of the # cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings # message to sign or verify. It only accepts bytestrings
subffill.sign(tx_serialized.encode(), private_key) subffill.sign(tx_serialized.encode(), private_key)
self.inputs[index] = input self.inputs[index] = input_
def inputs_valid(self, outputs=None): def inputs_valid(self, outputs=None):
"""Validates the Inputs in the Transaction against given """Validates the Inputs in the Transaction against given
@ -974,7 +981,7 @@ class Transaction(object):
Note: Note:
Given a `CREATE` or `GENESIS` Transaction is passed, Given a `CREATE` or `GENESIS` Transaction is passed,
dummyvalues for Outputs are submitted for validation that dummy values for Outputs are submitted for validation that
evaluate parts of the validation-checks to `True`. evaluate parts of the validation-checks to `True`.
Args: Args:
@ -1000,75 +1007,76 @@ class Transaction(object):
raise TypeError('`operation` must be one of {}' raise TypeError('`operation` must be one of {}'
.format(allowed_ops)) .format(allowed_ops))
def _inputs_valid(self, output_uris): def _inputs_valid(self, output_condition_uris):
"""Validates an Input against a given set of Outputs. """Validates an Input against a given set of Outputs.
Note: Note:
The number of `output_uris` must be equal to the The number of `output_condition_uris` must be equal to the
number of Inputs a Transaction has. number of Inputs a Transaction has.
Args: Args:
output_uris (:obj:`list` of :obj:`str`): A list of output_condition_uris (:obj:`list` of :obj:`str`): A list of
Outputs to check the Inputs against. Outputs to check the Inputs against.
Returns: Returns:
bool: If all Outputs are valid. bool: If all Outputs are valid.
""" """
if len(self.inputs) != len(output_uris): if len(self.inputs) != len(output_condition_uris):
raise ValueError('Inputs and ' raise ValueError('Inputs and '
'output_uris must have the same count') 'output_condition_uris must have the same count')
def gen_tx(input, output, output_uri=None): def gen_tx(input_, output, output_condition_uri=None):
"""Splits multiple IO Transactions into partial single IO """Splits multiple IO Transactions into partial single IO
Transactions. Transactions.
""" """
tx = Transaction(self.operation, self.asset, [input], tx = Transaction(self.operation, self.asset, [input_],
self.outputs, self.metadata, self.version) self.outputs, self.metadata, self.version)
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict) tx_dict = Transaction._remove_signatures(tx_dict)
tx_serialized = Transaction._to_str(tx_dict) tx_serialized = Transaction._to_str(tx_dict)
# TODO: Use local reference to class, not `Transaction.` return self.__class__._input_valid(input_,
return Transaction._input_valid(input, self.operation, self.operation,
tx_serialized, output_uri) tx_serialized,
output_condition_uri)
partial_transactions = map(gen_tx, self.inputs, partial_transactions = map(gen_tx, self.inputs,
self.outputs, output_uris) self.outputs, output_condition_uris)
return all(partial_transactions) return all(partial_transactions)
@staticmethod @staticmethod
def _input_valid(input, operation, tx_serialized, output_uri=None): def _input_valid(input_, operation, tx_serialized, output_condition_uri=None):
"""Validates a single Input against a single Output. """Validates a single Input against a single Output.
Note: Note:
In case of a `CREATE` or `GENESIS` Transaction, this method In case of a `CREATE` or `GENESIS` Transaction, this method
does not validate against `output_uri`. does not validate against `output_condition_uri`.
Args: Args:
input (:class:`~bigchaindb.common.transaction. input_ (:class:`~bigchaindb.common.transaction.
Input`) The Input to be signed. Input`) The Input to be signed.
operation (str): The type of Transaction. operation (str): The type of Transaction.
tx_serialized (str): The Transaction used as a message when tx_serialized (str): The Transaction used as a message when
initially signing it. initially signing it.
output_uri (str, optional): An Output to check the output_condition_uri (str, optional): An Output to check the
Input against. Input against.
Returns: Returns:
bool: If the Input is valid. bool: If the Input is valid.
""" """
ccffill = input.fulfillment ccffill = input_.fulfillment
try: try:
parsed_ffill = CCFulfillment.from_uri(ccffill.serialize_uri()) parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri())
except (TypeError, ValueError, ParsingError): except (TypeError, ValueError, ParsingError):
return False return False
if operation in (Transaction.CREATE, Transaction.GENESIS): if operation in (Transaction.CREATE, Transaction.GENESIS):
# NOTE: In the case of a `CREATE` or `GENESIS` transaction, the # NOTE: In the case of a `CREATE` or `GENESIS` transaction, the
# output is always validate to `True`. # output is always valid.
output_valid = True output_valid = True
else: else:
output_valid = output_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 # NOTE: We pass a timestamp to `.validate`, as in case of a timeout
# condition we'll have to validate against it # condition we'll have to validate against it
@ -1092,7 +1100,7 @@ class Transaction(object):
asset = {'id': self.asset.data_id} asset = {'id': self.asset.data_id}
tx = { tx = {
'inputs': [input.to_dict() for input in self.inputs], 'inputs': [input_.to_dict() for input_ in self.inputs],
'outputs': [output.to_dict() for output in self.outputs], 'outputs': [output.to_dict() for output in self.outputs],
'operation': str(self.operation), 'operation': str(self.operation),
'metadata': self.metadata, 'metadata': self.metadata,
@ -1122,12 +1130,12 @@ class Transaction(object):
# NOTE: We remove the reference since we need `tx_dict` only for the # NOTE: We remove the reference since we need `tx_dict` only for the
# transaction's hash # transaction's hash
tx_dict = deepcopy(tx_dict) tx_dict = deepcopy(tx_dict)
for input in tx_dict['inputs']: for input_ in tx_dict['inputs']:
# NOTE: Not all Cryptoconditions return a `signature` key (e.g. # NOTE: Not all Cryptoconditions return a `signature` key (e.g.
# ThresholdSha256Fulfillment), so setting it to `None` in any # ThresholdSha256Fulfillment), so setting it to `None` in any
# case could yield incorrect signatures. This is why we only # case could yield incorrect signatures. This is why we only
# set it to `None` if it's set in the dict. # set it to `None` if it's set in the dict.
input['fulfillment'] = None input_['fulfillment'] = None
return tx_dict return tx_dict
@staticmethod @staticmethod
@ -1184,7 +1192,7 @@ class Transaction(object):
:class:`~bigchaindb.common.transaction.Transaction` :class:`~bigchaindb.common.transaction.Transaction`
""" """
cls.validate_structure(tx) cls.validate_structure(tx)
inputs = [Input.from_dict(input) for input in tx['inputs']] inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
outputs = [Output.from_dict(output) for output in tx['outputs']] outputs = [Output.from_dict(output) for output in tx['outputs']]
if tx['operation'] in [cls.CREATE, cls.GENESIS]: if tx['operation'] in [cls.CREATE, cls.GENESIS]:
asset = Asset.from_dict(tx['asset']) asset = Asset.from_dict(tx['asset'])

View File

@ -424,17 +424,17 @@ class Bigchain(object):
# use it after the execution of this function. # use it after the execution of this function.
# a transaction can contain multiple outputs 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 # to get a list of outputs available to spend
for index, out in enumerate(tx['outputs']): for index, output in enumerate(tx['outputs']):
# for simple signature conditions there are no subfulfillments # for simple signature conditions there are no subfulfillments
# check if the owner is in the condition `owners_after` # check if the owner is in the condition `owners_after`
if len(out['public_keys']) == 1: if len(output['public_keys']) == 1:
if out['condition']['details']['public_key'] == owner: if output['condition']['details']['public_key'] == owner:
tx_link = TransactionLink(tx['id'], index) tx_link = TransactionLink(tx['id'], index)
else: else:
# for transactions with multiple `public_keys` 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 # in the condition. We need to iterate the subfulfillments to make sure there is a
# subfulfillment for `owner` # subfulfillment for `owner`
if util.condition_details_has_owner(out['condition']['details'], owner): if util.condition_details_has_owner(output['condition']['details'], owner):
tx_link = TransactionLink(tx['id'], index) tx_link = TransactionLink(tx['id'], index)
# check if input was already spent # check if input was already spent
if not self.get_spent(tx_link.txid, tx_link.idx): if not self.get_spent(tx_link.txid, tx_link.idx):

View File

@ -37,14 +37,14 @@ class Transaction(Transaction):
raise ValueError('Transaction contains no inputs') raise ValueError('Transaction contains no inputs')
input_conditions = [] input_conditions = []
inputs_defined = all([inp.fulfills for inp in self.inputs]) inputs_defined = all([input_.fulfills for input_ in self.inputs])
if self.operation in (Transaction.CREATE, Transaction.GENESIS): if self.operation in (Transaction.CREATE, Transaction.GENESIS):
# validate inputs # validate inputs
if inputs_defined: if inputs_defined:
raise ValueError('A CREATE operation has no inputs') raise ValueError('A CREATE operation has no inputs')
# validate asset # validate asset
amount = sum([out.amount for out in self.outputs]) amount = sum([output.amount for output in self.outputs])
self.asset.validate_asset(amount=amount) self.asset.validate_asset(amount=amount)
elif self.operation == Transaction.TRANSFER: elif self.operation == Transaction.TRANSFER:
if not inputs_defined: if not inputs_defined:

View File

@ -18,17 +18,19 @@ 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 e.g. 700 oak trees. Divisible assets can be split apart or recombined
by transfer transactions (described more below). by transfer transactions (described more below).
A CREATE transaction also establishes the conditions (outputs) that must be met to A CREATE transaction also establishes, in its outputs, the conditions that must
transfer the asset(s). For example, there may be a condition that any transfer be met to transfer the asset(s). The conditions may also be associated with a
must be signed (cryptographically) by the private key associated with a list of public keys that, depending on the condition, may have full or partial
given public key. More sophisticated conditions are possible. control over the asset(s). For example, there may be a condition that any
BigchainDB's conditions are based on the crypto-conditions of the [Interledger transfer must be signed (cryptographically) by the private key associated with a
Protocol (ILP)](https://interledger.org/). 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 ## TRANSFER Transactions
A TRANSFER transaction can transfer an asset A TRANSFER transaction can transfer an asset
by provding inputs which fulfill the current output conditions on the asset. by providing inputs which fulfill the current output conditions on the asset.
It must also specify new transfer conditions. It must also specify new transfer conditions.
**Example 1:** Suppose a red car is owned and controlled by Joe. **Example 1:** Suppose a red car is owned and controlled by Joe.

View File

@ -72,12 +72,12 @@ Transaction
%(transaction)s %(transaction)s
Input Input
---------- -----
%(input)s %(input)s
Output Output
----------- ------
%(output)s %(output)s

View File

@ -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: 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, inputs, outputs and other things 1. Transactions, which contain digital assets, inputs, outputs, and other things
2. Blocks 2. Blocks
3. Votes 3. Votes

View File

@ -832,10 +832,10 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
'inputs': [ 'inputs': [
{ {
'owners_before': [ 'owners_before': [
user_pub user_pub,
], ],
'fulfillment': None, 'fulfillment': None,
'fulfills': None 'fulfills': None,
}, },
], ],
'operation': 'CREATE', 'operation': 'CREATE',