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