Exemple #1
0
    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.
                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
                    Output.

            Raises:
                TypeError: if `public_keys` is not instance of `list`.
        """
        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:
            raise AmountError('`amount` must be greater than 0')
        if amount > self.MAX_AMOUNT:
            raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT)

        self.fulfillment = fulfillment
        self.amount = amount
        self.public_keys = public_keys
    def validate_asset(self, amount=None):
        """Validates the asset"""
        if self.data is not None and not isinstance(self.data, dict):
            raise TypeError('`data` must be a dict instance or None')
        if not isinstance(self.divisible, bool):
            raise TypeError('`divisible` must be a boolean')
        if not isinstance(self.refillable, bool):
            raise TypeError('`refillable` must be a boolean')
        if not isinstance(self.updatable, bool):
            raise TypeError('`updatable` must be a boolean')

        if self.refillable:
            raise NotImplementedError('Refillable assets are not yet'
                                      ' implemented')
        if self.updatable:
            raise NotImplementedError('Updatable assets are not yet'
                                      ' implemented')

        # If the amount is supplied we can perform extra validations to
        # the asset
        if amount is not None:
            if not isinstance(amount, int):
                raise TypeError('`amount` must be an int')

            if self.divisible is False and amount != 1:
                raise AmountError('non divisible assets always have'
                                  ' amount equal to one')

            # Since refillable assets are not yet implemented this should
            # raise and exception
            if self.divisible is True and amount < 2:
                raise AmountError('divisible assets must have an amount'
                                  ' greater than one')
Exemple #3
0
    def from_dict(cls, data):
        """Transforms a Python dictionary to an Output object.

            Note:
                To pass a serialization cycle multiple times, a
                Cryptoconditions Fulfillment needs to be present in the
                passed-in dictionary, as Condition URIs are not serializable
                anymore.

            Args:
                data (dict): The dict to be transformed.

            Returns:
                :class:`~bigchaindb.common.transaction.Output`
        """
        try:
            fulfillment = _fulfillment_from_details(
                data['condition']['details'])
        except KeyError:
            # NOTE: Hashlock condition case
            fulfillment = data['condition']['uri']
        try:
            amount = int(data['amount'])
        except ValueError:
            raise AmountError('Invalid amount: %s' % data['amount'])
        return cls(fulfillment, data['public_keys'], amount)
Exemple #4
0
    def validate_transfer_inputs(self, bigchain, current_transactions=[]):
        # store the inputs so that we can check if the asset ids match
        input_txs = []
        input_conditions = []
        for input_ in self.inputs:
            input_txid = input_.fulfills.txid
            input_tx = bigchain.get_transaction(input_txid)

            if input_tx is None:
                for ctxn in current_transactions:
                    if ctxn.id == input_txid:
                        input_tx = ctxn

            if input_tx is None:
                raise InputDoesNotExist(
                    "input `{}` doesn't exist".format(input_txid))

            spent = bigchain.get_spent(input_txid, input_.fulfills.output,
                                       current_transactions)
            if spent:
                raise DoubleSpend(
                    'input `{}` was already spent'.format(input_txid))

            output = input_tx.outputs[input_.fulfills.output]
            input_conditions.append(output)
            input_txs.append(input_tx)

        # Validate that all inputs are distinct
        links = [i.fulfills.to_uri() for i in self.inputs]
        if len(links) != len(set(links)):
            raise DoubleSpend('tx "{}" spends inputs twice'.format(self.id))

        # validate asset id
        asset_id = self.get_asset_id(input_txs)
        if asset_id != self.asset['id']:
            raise AssetIdMismatch(('The asset id of the input does not'
                                   ' match the asset id of the'
                                   ' transaction'))

        input_amount = sum(
            [input_condition.amount for input_condition in input_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 `{}`'
                 ' needs to be same as the amount used'
                 ' in the outputs `{}`').format(input_amount, output_amount))

        if not self.inputs_valid(input_conditions):
            raise InvalidSignature('Transaction signature is invalid.')

        return True
Exemple #5
0
    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
                is always the number of subconditions it is split between, a
                list of the following structure is sufficient:

                [(address|condition)*, [(address|condition)*, ...], ...]

            Args:
                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 Output.

            Returns:
                An Output that can be used in a Transaction.

            Raises:
                TypeError: If `public_keys` is not an instance of `list`.
                ValueError: If `public_keys` is an empty list.
        """
        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(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(public_keys) == 1 and not isinstance(public_keys[0], list):
            if isinstance(public_keys[0], Fulfillment):
                ffill = public_keys[0]
            else:
                ffill = Ed25519Sha256(
                    public_key=base58.b58decode(public_keys[0]))
            return cls(ffill, public_keys, amount=amount)
        else:
            initial_cond = ThresholdSha256(threshold=threshold)
            threshold_cond = reduce(cls._gen_condition, public_keys,
                                    initial_cond)
            return cls(threshold_cond, public_keys, amount=amount)
Exemple #6
0
    def validate(self, bigchain):
        """Validate a transaction.

        Args:
            bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.

        Returns:
            The transaction (Transaction) if the transaction is valid else it
            raises an exception describing the reason why the transaction is
            invalid.

        Raises:
            OperationError: if the transaction operation is not supported
            TransactionDoesNotExist: if the input of the transaction is not
                                     found
            TransactionNotInValidBlock: if the input of the transaction is not
                                        in a valid block
            TransactionOwnerError: if the new transaction is using an input it
                                   doesn't own
            DoubleSpend: if the transaction is a double spend
            InvalidHash: if the hash of the transaction is wrong
            InvalidSignature: if the signature of the transaction is wrong
        """
        if len(self.inputs) == 0:
            raise ValueError('Transaction contains no inputs')

        input_conditions = []
        inputs_defined = all([input_.fulfills for input_ in self.inputs])

        # validate amounts
        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):
            # validate asset
            if self.asset['data'] is not None and not isinstance(
                    self.asset['data'], dict):
                raise TypeError(('`asset.data` must be a dict instance or '
                                 'None for `CREATE` transactions'))
            # validate inputs
            if inputs_defined:
                raise ValueError('A CREATE operation has no inputs')
        elif self.operation == Transaction.TRANSFER:
            # validate asset
            if not isinstance(self.asset['id'], str):
                raise ValueError(('`asset.id` must be a string for '
                                  '`TRANSFER` transations'))
            # check inputs
            if not inputs_defined:
                raise ValueError('Only `CREATE` transactions can have null '
                                 'inputs')

            # store the inputs so that we can check if the asset ids match
            input_txs = []
            for input_ in self.inputs:
                input_txid = input_.fulfills.txid
                input_tx, status = bigchain.\
                    get_transaction(input_txid, include_status=True)

                if input_tx is None:
                    raise TransactionDoesNotExist(
                        "input `{}` doesn't exist".format(input_txid))

                if status != bigchain.TX_VALID:
                    raise TransactionNotInValidBlock(
                        'input `{}` does not exist in a valid block'.format(
                            input_txid))

                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))

                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 that all inputs are distinct
            links = [i.fulfills.to_uri() for i in self.inputs]
            if len(links) != len(set(links)):
                raise DoubleSpend('tx "{}" spends inputs twice'.format(
                    self.id))

            # validate asset id
            asset_id = Transaction.get_asset_id(input_txs)
            if asset_id != self.asset['id']:
                raise AssetIdMismatch(('The asset id of the input does not'
                                       ' 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.outputs])

            if output_amount != input_amount:
                raise AmountError(
                    ('The amount used in the inputs `{}`'
                     ' needs to be same as the amount used'
                     ' in the outputs `{}`').format(input_amount,
                                                    output_amount))

        else:
            allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
            raise TypeError('`operation`: `{}` must be either {}.'.format(
                self.operation, allowed_operations))

        if not self.inputs_valid(input_conditions):
            raise InvalidSignature('Transaction signature is invalid.')

        return self
Exemple #7
0
    def validate(self, bigchain):
        """Validate transaction spend

        Args:
            bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.

        Returns:
            The transaction (Transaction) if the transaction is valid else it
            raises an exception describing the reason why the transaction is
            invalid.

        Raises:
            ValidationError: If the transaction is invalid
        """
        input_conditions = []

        if self.operation == Transaction.TRANSFER:
            # store the inputs so that we can check if the asset ids match
            input_txs = []
            for input_ in self.inputs:
                input_txid = input_.fulfills.txid
                input_tx, status = bigchain.\
                    get_transaction(input_txid, include_status=True)

                if input_tx is None:
                    raise InputDoesNotExist(
                        "input `{}` doesn't exist".format(input_txid))

                if status != bigchain.TX_VALID:
                    raise TransactionNotInValidBlock(
                        'input `{}` does not exist in a valid block'.format(
                            input_txid))

                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))

                output = input_tx.outputs[input_.fulfills.output]
                input_conditions.append(output)
                input_txs.append(input_tx)

            # Validate that all inputs are distinct
            links = [i.fulfills.to_uri() for i in self.inputs]
            if len(links) != len(set(links)):
                raise DoubleSpend('tx "{}" spends inputs twice'.format(
                    self.id))

            # validate asset id
            asset_id = Transaction.get_asset_id(input_txs)
            if asset_id != self.asset['id']:
                raise AssetIdMismatch(('The asset id of the input does not'
                                       ' match the asset id of the'
                                       ' transaction'))

            input_amount = sum([
                input_condition.amount for input_condition in input_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 `{}`'
                     ' needs to be same as the amount used'
                     ' in the outputs `{}`').format(input_amount,
                                                    output_amount))

        if not self.inputs_valid(input_conditions):
            raise InvalidSignature('Transaction signature is invalid.')

        return self
Exemple #8
0
    def validate(self, bigchain):
        """Validate a transaction.

        Args:
            bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.

        Returns:
            The transaction (Transaction) if the transaction is valid else it
            raises an exception describing the reason why the transaction is
            invalid.

        Raises:
            OperationError: if the transaction operation is not supported
            TransactionDoesNotExist: if the input of the transaction is not
                                     found
            TransactionNotInValidBlock: if the input of the transaction is not
                                        in a valid block
            TransactionOwnerError: if the new transaction is using an input it
                                   doesn't own
            DoubleSpend: if the transaction is a double spend
            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')

        input_conditions = []
        inputs_defined = all([ffill.tx_input for ffill in self.fulfillments])

        if self.operation in (Transaction.CREATE, Transaction.GENESIS):
            # validate inputs
            if inputs_defined:
                raise ValueError('A CREATE operation has no inputs')
            # validate asset
            amount = sum([condition.amount for condition in self.conditions])
            self.asset.validate_asset(amount=amount)
        elif self.operation == Transaction.TRANSFER:
            if not inputs_defined:
                raise ValueError('Only `CREATE` transactions can have null '
                                 'inputs')
            # check inputs
            # store the inputs so that we can check if the asset ids match
            input_txs = []
            input_amount = 0
            for ffill in self.fulfillments:
                input_txid = ffill.tx_input.txid
                input_cid = ffill.tx_input.cid
                input_tx, status = bigchain.\
                    get_transaction(input_txid, include_status=True)

                if input_tx is None:
                    raise TransactionDoesNotExist(
                        "input `{}` doesn't exist".format(input_txid))

                if status != bigchain.TX_VALID:
                    raise TransactionNotInValidBlock(
                        'input `{}` does not exist in a valid block'.format(
                            input_txid))

                spent = bigchain.get_spent(input_txid, ffill.tx_input.cid)
                if spent and spent.id != self.id:
                    raise DoubleSpend(
                        'input `{}` was already spent'.format(input_txid))

                input_conditions.append(input_tx.conditions[input_cid])
                input_txs.append(input_tx)
                if input_tx.conditions[input_cid].amount < 1:
                    raise AmountError('`amount` needs to be greater than zero')
                input_amount += input_tx.conditions[input_cid].amount

            # validate asset id
            asset_id = Asset.get_asset_id(input_txs)
            if asset_id != self.asset.data_id:
                raise AssetIdMismatch(('The asset id of the input does not'
                                       ' match the asset id of the'
                                       ' transaction'))

            # get the asset creation to see if its divisible or not
            asset = bigchain.get_asset_by_id(asset_id)
            # validate the asset
            asset.validate_asset(amount=input_amount)
            # validate the amounts
            output_amount = 0
            for condition in self.conditions:
                if condition.amount < 1:
                    raise AmountError('`amount` needs to be greater than zero')
                output_amount += condition.amount

            if output_amount != input_amount:
                raise AmountError(
                    ('The amount used in the inputs `{}`'
                     ' needs to be same as the amount used'
                     ' in the outputs `{}`').format(input_amount,
                                                    output_amount))

        else:
            allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
            raise TypeError('`operation`: `{}` must be either {}.'.format(
                self.operation, allowed_operations))

        if not self.fulfillments_valid(input_conditions):
            raise InvalidSignature()
        else:
            return self