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_blocks_status_containing_tx(self, txid): """Retrieve block ids and statuses related to a transaction Transactions may occur in multiple blocks, but no more than one valid block. Args: txid (str): transaction id of the transaction to query Returns: A dict of blocks containing the transaction, e.g. {block_id_1: 'valid', block_id_2: 'invalid' ...}, or None """ # First, get information on all blocks which contain this transaction blocks = self.search_block_election_on_index(txid, 'transaction_id') if blocks: # Determine the election status of each block validity = { block['id']: self.block_election_status(block['id'], block['block']['voters']) for block in blocks } # NOTE: If there are multiple valid blocks with this transaction, # something has gone wrong if list(validity.values()).count(Bigchain.BLOCK_VALID) > 1: block_ids = str([ block for block in validity if validity[block] == Bigchain.BLOCK_VALID ]) raise exceptions.DoubleSpend('Transaction {tx} is present in ' 'multiple valid blocks: ' '{block_ids}'.format( tx=txid, block_ids=block_ids)) return validity else: return None