def get_spent(self, txid, output, current_transactions=[]): transactions = backend.query.get_spent(self.connection, txid, output) transactions = list(transactions) if transactions else [] for ctxn in current_transactions: for ctxn_input in ctxn.inputs: if ctxn_input.fulfills.txid == txid and\ ctxn_input.fulfills.output == output: transactions.append(ctxn.to_dict()) transaction = None if len(transactions) > 1: raise core_exceptions.CriticalDoubleSpend( '`{}` was spent more than once. There is a problem' ' with the chain'.format(txid)) elif transactions: transaction = transactions[0] if transaction and transaction['operation'] == 'CREATE': asset = backend.query.get_asset(self.connection, transaction['id']) if asset: transaction['asset'] = asset else: transaction['asset'] = {'data': None} return Transaction.from_dict(transaction) elif transaction and transaction['operation'] == 'TRANSFER': return Transaction.from_dict(transaction) else: return None
def test_create_single_input(b, create_tx, alice): from bigchaindb.common.transaction import Transaction tx = create_tx.to_dict() tx['inputs'] += tx['inputs'] tx = Transaction.from_dict(tx).sign([alice.private_key]).to_dict() validate_raises(tx) tx['id'] = None tx['inputs'] = [] tx = Transaction.from_dict(tx).sign([alice.private_key]).to_dict() validate_raises(tx)
def test_transfer_asset_schema(user_sk, signed_transfer_tx): from bigchaindb.common.transaction import Transaction tx = signed_transfer_tx.to_dict() validate(tx) tx['id'] = None tx['asset']['data'] = {} tx = Transaction.from_dict(tx).sign([user_sk]).to_dict() validate_raises(tx) tx['id'] = None del tx['asset']['data'] tx['asset']['id'] = 'b' * 63 tx = Transaction.from_dict(tx).sign([user_sk]).to_dict() validate_raises(tx)
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] monitor = current_app.config['monitor'] # `force` will try to format the body of the POST request even if the `content-type` header is not # set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except (ValidationError, InvalidSignature): return make_error(400, 'Invalid transaction') with pool() as bigchain: if bigchain.is_valid_transaction(tx_obj): rate = bigchaindb.config['statsd']['rate'] with monitor.timer('write_transaction', rate=rate): bigchain.write_transaction(tx_obj) else: return make_error(400, 'Invalid transaction') return tx
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ tx = Transaction.from_dict(tx) if self.bigchain.transaction_exists(tx.id): # if the transaction already exists, we must check whether # it's in a valid or undecided block tx, status = self.bigchain.get_transaction(tx.id, include_status=True) if status == self.bigchain.TX_VALID \ or status == self.bigchain.TX_UNDECIDED: # if the tx is already in a valid or undecided block, # then it no longer should be in the backlog, or added # to a new block. We can delete and drop it. self.bigchain.delete_transaction(tx.id) return None tx_validated = self.bigchain.is_valid_transaction(tx) if tx_validated: return tx else: # if the transaction is not valid, remove it from the # backlog self.bigchain.delete_transaction(tx.id) return None
def get_tx_by_payload_uuid(self, payload_uuid): """Retrieves transactions related to a digital asset. When creating a transaction one of the optional arguments is the `payload`. The payload is a generic dict that contains information about the digital asset. To make it easy to query BigchainDB for that digital asset we create a UUID for the payload and store it with the transaction. This makes it easy for developers to keep track of their digital assets in bigchain. Args: payload_uuid (str): the UUID for this particular payload. Returns: A list of transactions containing that payload. If no transaction exists with that payload it returns an empty list `[]` """ cursor = self.connection.run( r.table('bigchain', read_mode=self.read_mode).get_all( payload_uuid, index='payload_uuid').concat_map(lambda block: block['block'][ 'transactions']).filter(lambda transaction: transaction[ 'transaction']['data']['uuid'] == payload_uuid)) transactions = list(cursor) return [Transaction.from_dict(tx) for tx in transactions]
def validate_tx(self, tx_dict, block_id, num_tx): """Validate a transaction. Transaction must also not be in any VALID block. Args: tx_dict (dict): the transaction to validate block_id (str): the id of block containing the transaction num_tx (int): the total number of transactions to process Returns: Three values are returned, the validity of the transaction, ``block_id``, ``num_tx``. """ try: tx = Transaction.from_dict(tx_dict) new = self.bigchain.is_new_transaction(tx.id, exclude_block_id=block_id) if not new: raise exceptions.ValidationError('Tx already exists, %s', tx.id) tx.validate(self.bigchain) valid = True except exceptions.ValidationError as e: valid = False logger.warning('Invalid tx: %s', e) return valid, block_id, num_tx
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] monitor = current_app.config['monitor'] # `force` will try to format the body of the POST request even if the `content-type` header is not # set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except (InvalidHash, InvalidSignature): return make_error(400, 'Invalid transaction') with pool() as bigchain: if bigchain.is_valid_transaction(tx_obj): rate = bigchaindb.config['statsd']['rate'] with monitor.timer('write_transaction', rate=rate): bigchain.write_transaction(tx_obj) else: return make_error(400, 'Invalid transaction') return tx
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ try: tx = Transaction.from_dict(tx) except ValidationError: return None # If transaction is in any VALID or UNDECIDED block we # should not include it again if not self.bigchain.is_new_transaction(tx.id): self.bigchain.delete_transaction(tx.id) return None # If transaction is not valid it should not be included try: tx.validate(self.bigchain) return tx except ValidationError as e: logger.warning('Invalid tx: %s', e) self.bigchain.delete_transaction(tx.id) return None
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error(400, message='Invalid transaction schema: {}'.format( e.__cause__.message)) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) else: bigchain.write_transaction(tx_obj) return tx, 202
def get_tx_by_metadata_id(self, metadata_id): """Retrieves transactions related to a metadata. When creating a transaction one of the optional arguments is the `metadata`. The metadata is a generic dict that contains extra information that can be appended to the transaction. To make it easy to query the bigchain for that particular metadata we create a UUID for the metadata and store it with the transaction. Args: metadata_id (str): the id for this particular metadata. Returns: A list of transactions containing that metadata. If no transaction exists with that metadata it returns an empty list `[]` """ cursor = self.connection.run( r.table('bigchain', read_mode=self.read_mode).get_all( metadata_id, index='metadata_id').concat_map(lambda block: block['block'][ 'transactions']).filter(lambda transaction: transaction[ 'transaction']['metadata']['id'] == metadata_id)) transactions = list(cursor) return [Transaction.from_dict(tx) for tx in transactions]
def test_create_tx_no_fulfills(b, create_tx): from bigchaindb.common.transaction import Transaction tx = create_tx.to_dict() tx['inputs'][0]['fulfills'] = {'transaction_id': 'a' * 64, 'output_index': 0} tx = Transaction.from_dict(tx).sign([b.me_private]).to_dict() validate_raises(tx)
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ try: tx = Transaction.from_dict(tx) except (SchemaValidationError, InvalidHash, InvalidSignature, AmountError): return None # If transaction is in any VALID or UNDECIDED block we # should not include it again if not self.bigchain.is_new_transaction(tx.id): self.bigchain.delete_transaction(tx.id) return None # If transaction is not valid it should not be included if not self.bigchain.is_valid_transaction(tx): self.bigchain.delete_transaction(tx.id) return None return tx
def get_transaction(self, txid, include_status=False): """Retrieve a transaction with `txid` from bigchain. Queries the bigchain for a transaction, if it's in a valid or invalid block. Args: txid (str): transaction id of the transaction to query include_status (bool): also return the status of the transaction the return value is then a tuple: (tx, status) Returns: A :class:`~.models.Transaction` instance if the transaction was found, otherwise ``None``. If :attr:`include_status` is ``True``, also returns the transaction's status if the transaction was found. """ response, tx_status = None, None validity = self.get_blocks_status_containing_tx(txid) if validity: # Disregard invalid blocks, and return if there are no valid or undecided blocks validity = { _id: status for _id, status in validity.items() if status != Bigchain.BLOCK_INVALID } if validity: tx_status = self.TX_UNDECIDED # If the transaction is in a valid or any undecided block, return it. Does not check # if transactions in undecided blocks are consistent, but selects the valid block before # undecided ones for target_block_id in validity: if validity[target_block_id] == Bigchain.BLOCK_VALID: tx_status = self.TX_VALID break # Query the transaction in the target block and return response = self.backend.get_transaction_from_block( txid, target_block_id) else: # Otherwise, check the backlog response = self.backend.get_transaction_from_backlog(txid) if response: tx_status = self.TX_IN_BACKLOG if response: response = Transaction.from_dict(response) if include_status: return response, tx_status else: return response
def test_memoize_from_dict(b): alice = generate_key_pair() asset = { 'data': { 'id': 'test_id' }, } assert from_dict.cache_info().hits == 0 assert from_dict.cache_info().misses == 0 tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=asset,)\ .sign([alice.private_key]) tx_dict = deepcopy(tx.to_dict()) Transaction.from_dict(tx_dict) assert from_dict.cache_info().hits == 0 assert from_dict.cache_info().misses == 1 Transaction.from_dict(tx_dict) Transaction.from_dict(tx_dict) assert from_dict.cache_info().hits == 2 assert from_dict.cache_info().misses == 1
def test_memoize_from_dict(b): alice = generate_key_pair() asset = { 'data': {'id': 'test_id'}, } assert from_dict.cache_info().hits == 0 assert from_dict.cache_info().misses == 0 tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset=asset,)\ .sign([alice.private_key]) tx_dict = deepcopy(tx.to_dict()) Transaction.from_dict(tx_dict) assert from_dict.cache_info().hits == 0 assert from_dict.cache_info().misses == 1 Transaction.from_dict(tx_dict) Transaction.from_dict(tx_dict) assert from_dict.cache_info().hits == 2 assert from_dict.cache_info().misses == 1
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 given `(txid, output)` is only used once. This method will check if the `(txid, output)` has already been spent in a transaction that is in either the `VALID`, `UNDECIDED` or `BACKLOG` state. Args: txid (str): The id of the transaction output (num): the index of the output in the respective transaction Returns: The transaction (Transaction) that used the `(txid, output)` as an input else `None` Raises: CriticalDoubleSpend: If the given `(txid, output)` was spent in more than one valid transaction. """ # checks if an input was already spent # 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 # determine if these valid transactions appear in more than one valid # block num_valid_transactions = 0 non_invalid_transactions = [] for transaction in transactions: # ignore transactions in invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? txn, status = self.get_transaction(transaction['id'], include_status=True) if status == self.TX_VALID: num_valid_transactions += 1 # `txid` can only have been spent in at most on valid block. if num_valid_transactions > 1: raise core_exceptions.CriticalDoubleSpend( '`{}` was spent more than once. There is a problem' ' with the chain'.format(txid)) # if its not and invalid transaction if status is not None: transaction.update({'metadata': txn.metadata}) non_invalid_transactions.append(transaction) if non_invalid_transactions: return Transaction.from_dict(non_invalid_transactions[0])
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 given `(txid, output)` is only used once. This method will check if the `(txid, output)` has already been spent in a transaction that is in either the `VALID`, `UNDECIDED` or `BACKLOG` state. Args: txid (str): The id of the transaction output (num): the index of the output in the respective transaction Returns: The transaction (Transaction) that used the `(txid, output)` as an input else `None` Raises: CriticalDoubleSpend: If the given `(txid, output)` was spent in more than one valid transaction. """ # checks if an input was already spent # 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 # determine if these valid transactions appear in more than one valid # block num_valid_transactions = 0 non_invalid_transactions = [] for transaction in transactions: # ignore transactions in invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? txn, status = self.get_transaction(transaction['id'], include_status=True) if status == self.TX_VALID: num_valid_transactions += 1 # `txid` can only have been spent in at most on valid block. if num_valid_transactions > 1: raise core_exceptions.CriticalDoubleSpend( '`{}` was spent more than once. There is a problem' ' with the chain'.format(txid)) # if its not and invalid transaction if status is not None: transaction.update({'metadata': txn.metadata}) non_invalid_transactions.append(transaction) if non_invalid_transactions: return Transaction.from_dict(non_invalid_transactions[0])
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error( 400, message='Invalid transaction schema: {}'.format( e.__cause__.message) ) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) with pool() as bigchain: bigchain.statsd.incr('web.tx.post') try: bigchain.validate_transaction(tx_obj) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) else: bigchain.write_transaction(tx_obj) response = jsonify(tx) response.status_code = 202 # NOTE: According to W3C, sending a relative URI is not allowed in the # Location Header: # - https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html # # Flask is autocorrecting relative URIs. With the following command, # we're able to prevent this. response.autocorrect_location_header = False status_monitor = '../statuses?transaction_id={}'.format(tx_obj.id) response.headers['Location'] = status_monitor return response
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error( 400, message='Invalid transaction schema: {}'.format( e.__cause__.message) ) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) else: bigchain.write_transaction(tx_obj) response = jsonify(tx) response.status_code = 202 # NOTE: According to W3C, sending a relative URI is not allowed in the # Location Header: # - https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html # # Flask is autocorrecting relative URIs. With the following command, # we're able to prevent this. response.autocorrect_location_header = False status_monitor = '../statuses?transaction_id={}'.format(tx_obj.id) response.headers['Location'] = status_monitor return response
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ parser = reqparse.RequestParser() parser.add_argument('mode', type=parameters.valid_mode, default='broadcast_tx_async') args = parser.parse_args() mode = str(args['mode']) pool = current_app.config['bigchain_pool'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error( 400, message='Invalid transaction schema: {}'.format( e.__cause__.message) ) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) else: status_code, message = bigchain.write_transaction(tx_obj, mode) if status_code == 202: response = jsonify(tx) response.status_code = 202 return response else: return make_error(status_code, message)
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] monitor = current_app.config['monitor'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error( 400, message='Invalid transaction schema: {}'.format( e.__cause__.message) ) except (ValidationError, InvalidSignature) as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except (ValueError, OperationError, TransactionDoesNotExist, TransactionOwnerError, DoubleSpend, InvalidHash, InvalidSignature, TransactionNotInValidBlock, AmountError) as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) else: rate = bigchaindb.config['statsd']['rate'] with monitor.timer('write_transaction', rate=rate): bigchain.write_transaction(tx_obj) return tx
def get_spent(self, txid, cid): """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 given `txid` is only used once. Args: txid (str): The id of the transaction cid (num): the index of the condition 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': ...} response = self.connection.run( r.table('bigchain', read_mode=self.read_mode). concat_map(lambda doc: doc['block']['transactions']).filter( lambda transaction: transaction['transaction']['fulfillments']. contains(lambda fulfillment: fulfillment['input'] == { 'txid': txid, 'cid': cid }))) transactions = list(response) # a transaction_id should have been spent at most one time if transactions: # determine if these valid transactions appear in more than one valid block num_valid_transactions = 0 for transaction in transactions: # ignore invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: raise exceptions.DoubleSpend( '`{}` was spent more then once. There is a problem with the chain' .format(txid)) if num_valid_transactions: return Transaction.from_dict(transactions[0]) else: # all queried transactions were invalid return None else: return None
def get_txs_by_asset_id(self, asset_id): """Retrieves transactions related to a particular asset. A digital asset in bigchaindb is identified by an uuid. This allows us to query all the transactions related to a particular digital asset, knowing the id. Args: asset_id (str): the id for this particular metadata. Returns: A list of transactions containing related to the asset. If no transaction exists for that asset it returns an empty list `[]` """ cursor = self.backend.get_transactions_by_asset_id(asset_id) return [Transaction.from_dict(tx) for tx in cursor]
def validate_transaction(self, tx, current_transactions=[]): """Validate a transaction against the current status of the database.""" transaction = tx # CLEANUP: The conditional below checks for transaction in dict format. # It would be better to only have a single format for the transaction # throught the code base. if isinstance(transaction, dict): try: transaction = Transaction.from_dict(tx) except SchemaValidationError as e: logger.warning('Invalid transaction schema: %s', e.__cause__.message) return False except ValidationError as e: logger.warning('Invalid transaction (%s): %s', type(e).__name__, e) return False return transaction.validate(self, current_transactions)
def get_tx_by_metadata_id(self, metadata_id): """Retrieves transactions related to a metadata. When creating a transaction one of the optional arguments is the `metadata`. The metadata is a generic dict that contains extra information that can be appended to the transaction. To make it easy to query the bigchain for that particular metadata we create a UUID for the metadata and store it with the transaction. Args: metadata_id (str): the id for this particular metadata. Returns: A list of transactions containing that metadata. If no transaction exists with that metadata it returns an empty list `[]` """ cursor = self.backend.get_transactions_by_metadata_id(metadata_id) return [Transaction.from_dict(tx) for tx in cursor]
def post(self): pool = current_app.config['bigchain_pool'] tx = request.get_json(force=True) tx_obj = Transaction.from_dict(tx) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except (ValueError, OperationError, TransactionDoesNotExist, TransactionOwnerError, FulfillmentNotInValidBlock, DoubleSpend, InvalidHash, InvalidSignature, AmountError) as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) else: bigchain.write_transaction(tx_obj) return tx, 202
def get_transaction(self, transaction_id): transaction = backend.query.get_transaction(self.connection, transaction_id) if transaction: asset = backend.query.get_asset(self.connection, transaction_id) metadata = backend.query.get_metadata(self.connection, [transaction_id]) if asset: transaction['asset'] = asset if 'metadata' not in transaction: metadata = metadata[0] if metadata else None if metadata: metadata = metadata.get('metadata') transaction.update({'metadata': metadata}) transaction = Transaction.from_dict(transaction) return transaction
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 given `txid` is only used once. Args: txid (str): The id of the 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': ..., # 'output': ...} transactions = list( backend.query.get_spent(self.connection, txid, output)) # a transaction_id should have been spent at most one time if transactions: # determine if these valid transactions appear in more than one valid block num_valid_transactions = 0 for transaction in transactions: # ignore invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: raise exceptions.DoubleSpend( ('`{}` was spent more than' ' once. There is a problem' ' with the chain').format(txid)) if num_valid_transactions: return Transaction.from_dict(transactions[0]) else: # all queried transactions were invalid return None else: return None
def get_txs_by_asset_id(self, asset_id): """Retrieves transactions related to a particular asset. A digital asset in bigchaindb is identified by an uuid. This allows us to query all the transactions related to a particular digital asset, knowing the id. Args: asset_id (str): the id for this particular metadata. Returns: A list of transactions containing related to the asset. If no transaction exists for that asset it returns an empty list `[]` """ cursor = self.connection.run( r.table('bigchain', read_mode=self.read_mode) .get_all(asset_id, index='asset_id') .concat_map(lambda block: block['block']['transactions']) .filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id)) return [Transaction.from_dict(tx) for tx in cursor]
def validate_transaction(self, tx, current_transactions=[]): """Validate a transaction against the current status of the database.""" transaction = tx if not isinstance(transaction, Transaction): try: transaction = Transaction.from_dict(tx) except SchemaValidationError as e: logger.warning('Invalid transaction schema: %s', e.__cause__.message) return False except ValidationError as e: logger.warning('Invalid transaction (%s): %s', type(e).__name__, e) return False try: return transaction.validate(self, current_transactions) except ValidationError as e: logger.warning('Invalid transaction (%s): %s', type(e).__name__, e) return False return transaction
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ try: tx = Transaction.from_dict(tx) except ValidationError: return None # If transaction is in any VALID or UNDECIDED block we # should not include it again if not self.bigchain.is_new_transaction(tx.id): self.bigchain.delete_transaction(tx.id) return None # If transaction is not valid it should not be included try: # Do not allow an externally submitted GENESIS transaction. # A simple check is enough as a pipeline is started only after the # creation of GENESIS block, or after the verification of a GENESIS # block. Voting will fail at a later stage if the GENESIS block is # absent. if tx.operation == Transaction.GENESIS: raise GenesisBlockAlreadyExistsError( 'Duplicate GENESIS transaction') tx.validate(self.bigchain) return tx except ValidationError as e: logger.warning('Invalid tx: %s', e) self.bigchain.delete_transaction(tx.id) return None
def validate_tx(self, tx): """Validate a transaction. Also checks if the transaction already exists in the blockchain. If it does, or it's invalid, it's deleted from the backlog immediately. Args: tx (dict): the transaction to validate. Returns: :class:`~bigchaindb.models.Transaction`: The transaction if valid, ``None`` otherwise. """ try: tx = Transaction.from_dict(tx) except ValidationError: return None # If transaction is in any VALID or UNDECIDED block we # should not include it again if not self.bigchain.is_new_transaction(tx.id): self.bigchain.delete_transaction(tx.id) return None # If transaction is not valid it should not be included try: # Do not allow an externally submitted GENESIS transaction. # A simple check is enough as a pipeline is started only after the # creation of GENESIS block, or after the verification of a GENESIS # block. Voting will fail at a later stage if the GENESIS block is # absent. if tx.operation == Transaction.GENESIS: raise GenesisBlockAlreadyExistsError('Duplicate GENESIS transaction') tx.validate(self.bigchain) return tx except ValidationError as e: logger.warning('Invalid tx: %s', e) self.bigchain.delete_transaction(tx.id) return None
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] monitor = current_app.config['monitor'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error(400, message='Invalid transaction schema: {}'.format( e.__cause__.message)) except (ValidationError, InvalidSignature) as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except (ValueError, OperationError, TransactionDoesNotExist, TransactionOwnerError, DoubleSpend, InvalidHash, InvalidSignature, TransactionNotInValidBlock, AmountError) as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e)) else: rate = bigchaindb.config['statsd']['rate'] with monitor.timer('write_transaction', rate=rate): bigchain.write_transaction(tx_obj) return tx
def get_spent(self, txid, cid): """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 given `txid` is only used once. Args: txid (str): The id of the transaction cid (num): the index of the condition 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)) # a transaction_id should have been spent at most one time if transactions: # determine if these valid transactions appear in more than one valid block num_valid_transactions = 0 for transaction in transactions: # ignore invalid blocks # FIXME: Isn't there a faster solution than doing I/O again? if self.get_transaction(transaction['id']): num_valid_transactions += 1 if num_valid_transactions > 1: raise exceptions.DoubleSpend(('`{}` was spent more than' ' once. There is a problem' ' with the chain') .format(txid)) if num_valid_transactions: return Transaction.from_dict(transactions[0]) else: # all queried transactions were invalid return None else: return None
def get_transaction(self, transaction_id, include_status=False): transaction = backend.query.get_transaction(self.connection, transaction_id) if transaction: asset = backend.query.get_asset(self.connection, transaction_id) metadata = backend.query.get_metadata(self.connection, [transaction_id]) if asset: transaction['asset'] = asset if 'metadata' not in transaction: metadata = metadata[0] if metadata else None if metadata: metadata = metadata.get('metadata') transaction.update({'metadata': metadata}) transaction = Transaction.from_dict(transaction) if include_status: return transaction, self.TX_VALID if transaction else None else: return transaction
def post(self): """API endpoint to push transactions to the Federation. Return: A ``dict`` containing the data about the transaction. """ pool = current_app.config['bigchain_pool'] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` tx = request.get_json(force=True) try: tx_obj = Transaction.from_dict(tx) except SchemaValidationError as e: return make_error( 400, message='Invalid transaction schema: {}'.format( e.__cause__.message) ) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) with pool() as bigchain: try: bigchain.validate_transaction(tx_obj) except ValidationError as e: return make_error( 400, 'Invalid transaction ({}): {}'.format(type(e).__name__, e) ) else: bigchain.write_transaction(tx_obj) return tx, 202
def post(self): print("createContractTx") pool = current_app.config['bigchain_pool'] contractTx = request.get_json(force=True) # print(111) print(contractTx) contractTx_obj = Transaction.from_dict(contractTx) # TODO validate data structure /version=2;opercation=create/transfer; has relation and contact? print("222====",contractTx_obj) with pool() as bigchain: try: # print("333") bigchain.validate_transaction(contractTx_obj) # print("444") except (ValueError, OperationError, TransactionDoesNotExist, TransactionOwnerError, FulfillmentNotInValidBlock, DoubleSpend, InvalidHash, InvalidSignature, AmountError) as e: return make_response(constant.RESPONSE_STATUS_PARAM_ERROE, constant.RESPONSE_CODE_PARAM_ERROE, "invalidate contract transaction.") # print(contractTx_obj.metadata.to_dict()) tx_result = bigchain.write_transaction(contractTx_obj) result_messages = "add contract transaction success" return make_response(constant.RESPONSE_STATUS_SUCCESS, constant.RESPONSE_CODE_SUCCESS, result_messages, tx_result)
def get_transaction(self, txid, include_status=False): """Get the transaction with the specified `txid` (and optionally its status) This query begins by looking in the bigchain table for all blocks containing a transaction with the specified `txid`. If one of those blocks is valid, it returns the matching transaction from that block. Else if some of those blocks are undecided, it returns a matching transaction from one of them. If the transaction was found in invalid blocks only, or in no blocks, then this query looks for a matching transaction in the backlog table, and if it finds one there, it returns that. Args: txid (str): transaction id of the transaction to get include_status (bool): also return the status of the transaction the return value is then a tuple: (tx, status) Returns: A :class:`~.models.Transaction` instance if the transaction was found in a valid block, an undecided block, or the backlog table, otherwise ``None``. If :attr:`include_status` is ``True``, also returns the transaction's status if the transaction was found. """ response, tx_status = None, None validity = self.get_blocks_status_containing_tx(txid) check_backlog = True if validity: # Disregard invalid blocks, and return if there are no valid or undecided blocks validity = {_id: status for _id, status in validity.items() if status != Bigchain.BLOCK_INVALID} if validity: # The transaction _was_ found in an undecided or valid block, # so there's no need to look in the backlog table check_backlog = False tx_status = self.TX_UNDECIDED # If the transaction is in a valid or any undecided block, return it. Does not check # if transactions in undecided blocks are consistent, but selects the valid block # before undecided ones for target_block_id in validity: if validity[target_block_id] == Bigchain.BLOCK_VALID: tx_status = self.TX_VALID break # Query the transaction in the target block and return response = backend.query.get_transaction_from_block(self.connection, txid, target_block_id) if check_backlog: response = backend.query.get_transaction_from_backlog(self.connection, txid) if response: tx_status = self.TX_IN_BACKLOG if response: response = Transaction.from_dict(response) if include_status: return response, tx_status else: return response