mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Problem: Bigchain class is unused and redundant (#2366)
* Problem: core.py contains an unused class, `Bigchain` Solution: Remove core.py. Refactor BigchainDB Class to remove inheritance from Bigchain. * Problem: core.py contains an unused class, `Bigchain` Solution: Remove core.py. Refactor BigchainDB Class to remove inheritance from Bigchain. * Fixed flake8 complaint about too many blank lines * Attempting to fix Sphinx docs. This may result in some redundant commits, as I don't know what I'm doing, and I can't experiment without running the CI... Sorry in advance! * Attempting to fix Sphinx docs. This may result in some redundant commits, as I don't know what I'm doing, and I can't experiment without running the CI... Sorry in advance! * Updating from master changed BigchainDB.process_post_response to a private method, so I had to align with that. * Fixed a couple stale references to bigchaindb.Bigchain in docstrings * Missed a reference to `Bigchain` in a patch call... * Merge with master and re-do some changes
This commit is contained in:
parent
f243b252ca
commit
4d2e58416c
@ -4,9 +4,9 @@ A high-level description of the files and subdirectories of BigchainDB.
|
||||
|
||||
## Files
|
||||
|
||||
### [`core.py`](./core.py)
|
||||
### [`tendermint/lib.py`](./tendermint/lib.py)
|
||||
|
||||
The `Bigchain` class is defined here. Most node-level operations and database interactions are found in this file. This is the place to start if you are interested in implementing a server API, since many of these class methods concern BigchainDB interacting with the outside world.
|
||||
The `BigchainDB` class is defined here. Most node-level operations and database interactions are found in this file. This is the place to start if you are interested in implementing a server API, since many of these class methods concern BigchainDB interacting with the outside world.
|
||||
|
||||
### [`models.py`](./models.py)
|
||||
|
||||
|
@ -84,5 +84,5 @@ config = {
|
||||
# the user wants to reconfigure the node. Check ``bigchaindb.config_utils``
|
||||
# for more info.
|
||||
_config = copy.deepcopy(config)
|
||||
from bigchaindb.core import Bigchain # noqa
|
||||
from bigchaindb.tendermint import BigchainDB # noqa
|
||||
from bigchaindb.version import __version__ # noqa
|
||||
|
@ -97,7 +97,7 @@ def run_configure(args):
|
||||
def run_upsert_validator(args):
|
||||
"""Store validators which should be synced with Tendermint"""
|
||||
|
||||
b = bigchaindb.tendermint.lib.BigchainDB()
|
||||
b = bigchaindb.tendermint.BigchainDB()
|
||||
public_key = public_key_from_base64(args.public_key)
|
||||
validator = {'pub_key': {'type': 'ed25519',
|
||||
'data': public_key},
|
||||
@ -113,7 +113,7 @@ def run_upsert_validator(args):
|
||||
|
||||
|
||||
def _run_init():
|
||||
bdb = bigchaindb.tendermint.lib.BigchainDB()
|
||||
bdb = bigchaindb.tendermint.BigchainDB()
|
||||
|
||||
schema.init_database(connection=bdb.connection)
|
||||
|
||||
|
@ -1,434 +0,0 @@
|
||||
from bigchaindb import exceptions as core_exceptions
|
||||
|
||||
import bigchaindb
|
||||
|
||||
from bigchaindb import backend, config_utils
|
||||
from bigchaindb.consensus import BaseConsensusRules
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
|
||||
class Bigchain(object):
|
||||
"""Bigchain API
|
||||
|
||||
Create, read, sign, write transactions to the database
|
||||
"""
|
||||
|
||||
BLOCK_INVALID = 'invalid'
|
||||
"""return if a block is invalid"""
|
||||
|
||||
BLOCK_VALID = TX_VALID = 'valid'
|
||||
"""return if a block is valid, or tx is in valid block"""
|
||||
|
||||
BLOCK_UNDECIDED = TX_UNDECIDED = 'undecided'
|
||||
"""return if block is undecided, or tx is in undecided block"""
|
||||
|
||||
TX_IN_BACKLOG = 'backlog'
|
||||
"""return if transaction is in backlog"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
"""Initialize the Bigchain instance
|
||||
|
||||
A Bigchain instance has several configuration parameters (e.g. host).
|
||||
If a parameter value is passed as an argument to the Bigchain
|
||||
__init__ method, then that is the value it will have.
|
||||
Otherwise, the parameter value will come from an environment variable.
|
||||
If that environment variable isn't set, then the value
|
||||
will come from the local configuration file. And if that variable
|
||||
isn't in the local configuration file, then the parameter will have
|
||||
its default value (defined in bigchaindb.__init__).
|
||||
|
||||
Args:
|
||||
connection (:class:`~bigchaindb.backend.connection.Connection`):
|
||||
A connection to the database.
|
||||
"""
|
||||
consensusPlugin = bigchaindb.config.get('consensus_plugin')
|
||||
|
||||
if consensusPlugin:
|
||||
self.consensus = config_utils.load_consensus_plugin(consensusPlugin)
|
||||
else:
|
||||
self.consensus = BaseConsensusRules
|
||||
|
||||
def delete_transaction(self, *transaction_id):
|
||||
"""Delete a transaction from the backlog.
|
||||
|
||||
Args:
|
||||
*transaction_id (str): the transaction(s) to delete
|
||||
|
||||
Returns:
|
||||
The database response.
|
||||
"""
|
||||
|
||||
return backend.query.delete_transaction(self.connection, *transaction_id)
|
||||
|
||||
def get_stale_transactions(self):
|
||||
"""Get a cursor of stale transactions.
|
||||
|
||||
Transactions are considered stale if they have been assigned a node, but are still in the
|
||||
backlog after some amount of time specified in the configuration
|
||||
"""
|
||||
|
||||
return backend.query.get_stale_transactions(self.connection)
|
||||
|
||||
def validate_transaction(self, transaction):
|
||||
"""Validate a transaction.
|
||||
|
||||
Args:
|
||||
transaction (Transaction): transaction to validate.
|
||||
|
||||
Returns:
|
||||
The transaction if the transaction is valid else it raises an
|
||||
exception describing the reason why the transaction is invalid.
|
||||
"""
|
||||
|
||||
return self.consensus.validate_transaction(self, transaction)
|
||||
|
||||
def is_new_transaction(self, txid, exclude_block_id=None):
|
||||
"""Return True if the transaction does not exist in any
|
||||
VALID or UNDECIDED block. Return False otherwise.
|
||||
|
||||
Args:
|
||||
txid (str): Transaction ID
|
||||
exclude_block_id (str): Exclude block from search
|
||||
"""
|
||||
block_statuses = self.get_blocks_status_containing_tx(txid)
|
||||
block_statuses.pop(exclude_block_id, None)
|
||||
for status in block_statuses.values():
|
||||
if status != self.BLOCK_INVALID:
|
||||
return False
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
blocks_validity_status = self.get_blocks_status_containing_tx(txid)
|
||||
check_backlog = True
|
||||
|
||||
if blocks_validity_status:
|
||||
# Disregard invalid blocks, and return if there are no valid or undecided blocks
|
||||
blocks_validity_status = {
|
||||
_id: status for _id, status in blocks_validity_status.items()
|
||||
if status != Bigchain.BLOCK_INVALID
|
||||
}
|
||||
if blocks_validity_status:
|
||||
|
||||
# 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 blocks_validity_status:
|
||||
if blocks_validity_status[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:
|
||||
if tx_status == self.TX_IN_BACKLOG:
|
||||
response = Transaction.from_dict(response)
|
||||
else:
|
||||
# If we are reading from the bigchain collection the asset is
|
||||
# not in the transaction so we need to fetch the asset and
|
||||
# reconstruct the transaction.
|
||||
response = Transaction.from_db(self, response)
|
||||
|
||||
if include_status:
|
||||
return response, tx_status
|
||||
else:
|
||||
return response
|
||||
|
||||
def get_status(self, txid):
|
||||
"""Retrieve the status of a transaction with `txid` from bigchain.
|
||||
|
||||
Args:
|
||||
txid (str): transaction id of the transaction to query
|
||||
|
||||
Returns:
|
||||
(string): transaction status ('valid', 'undecided',
|
||||
or 'backlog'). If no transaction with that `txid` was found it
|
||||
returns `None`
|
||||
"""
|
||||
_, status = self.get_transaction(txid, include_status=True)
|
||||
return status
|
||||
|
||||
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 = backend.query.get_blocks_status_from_transaction(self.connection, txid)
|
||||
if blocks:
|
||||
# Determine the election status of each block
|
||||
blocks_validity_status = {
|
||||
block['id']: self.block_election_status(block)
|
||||
for block in blocks
|
||||
}
|
||||
|
||||
# NOTE: If there are multiple valid blocks with this transaction,
|
||||
# something has gone wrong
|
||||
if list(blocks_validity_status.values()).count(Bigchain.BLOCK_VALID) > 1:
|
||||
block_ids = str([
|
||||
block for block in blocks_validity_status
|
||||
if blocks_validity_status[block] == Bigchain.BLOCK_VALID
|
||||
])
|
||||
raise core_exceptions.CriticalDoubleInclusion(
|
||||
'Transaction {tx} is present in '
|
||||
'multiple valid blocks: {block_ids}'
|
||||
.format(tx=txid, block_ids=block_ids))
|
||||
|
||||
return blocks_validity_status
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_asset_by_id(self, asset_id):
|
||||
"""Returns the asset associated with an asset_id.
|
||||
|
||||
Args:
|
||||
asset_id (str): The asset id.
|
||||
|
||||
Returns:
|
||||
dict if the asset exists else None.
|
||||
"""
|
||||
cursor = backend.query.get_asset_by_id(self.connection, asset_id)
|
||||
cursor = list(cursor)
|
||||
if cursor:
|
||||
return cursor[0]['asset']
|
||||
|
||||
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])
|
||||
|
||||
# Either no transaction was returned spending the `(txid, output)` as
|
||||
# input or the returned transactions are not valid.
|
||||
|
||||
def get_owned_ids(self, owner):
|
||||
"""Retrieve a list of ``txid`` s that can be used as inputs.
|
||||
|
||||
Args:
|
||||
owner (str): base58 encoded public key.
|
||||
|
||||
Returns:
|
||||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||
pointing to another transaction's condition
|
||||
"""
|
||||
return self.get_outputs_filtered(owner, spent=False)
|
||||
|
||||
def get_outputs_filtered(self, owner, spent=None):
|
||||
"""Get a list of output links filtered on some criteria
|
||||
|
||||
Args:
|
||||
owner (str): base58 encoded public_key.
|
||||
spent (bool): If ``True`` return only the spent outputs. If
|
||||
``False`` return only unspent outputs. If spent is
|
||||
not specified (``None``) return all outputs.
|
||||
|
||||
Returns:
|
||||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||
pointing to another transaction's condition
|
||||
"""
|
||||
outputs = self.fastquery.get_outputs_by_public_key(owner)
|
||||
if spent is None:
|
||||
return outputs
|
||||
elif spent is True:
|
||||
return self.fastquery.filter_unspent_outputs(outputs)
|
||||
elif spent is False:
|
||||
return self.fastquery.filter_spent_outputs(outputs)
|
||||
|
||||
def get_transactions_filtered(self, asset_id, operation=None):
|
||||
"""Get a list of transactions filtered on some criteria
|
||||
"""
|
||||
txids = backend.query.get_txids_filtered(self.connection, asset_id,
|
||||
operation)
|
||||
for txid in txids:
|
||||
tx, status = self.get_transaction(txid, True)
|
||||
if status == self.TX_VALID:
|
||||
yield tx
|
||||
|
||||
# TODO: check that the votings structure is correctly constructed
|
||||
def validate_block(self, block):
|
||||
"""Validate a block.
|
||||
|
||||
Args:
|
||||
block (Block): block to validate.
|
||||
|
||||
Returns:
|
||||
The block if the block is valid else it raises and exception
|
||||
describing the reason why the block is invalid.
|
||||
"""
|
||||
return self.consensus.validate_block(self, block)
|
||||
|
||||
def write_block(self, block):
|
||||
"""Write a block to bigchain.
|
||||
|
||||
Args:
|
||||
block (Block): block to write to bigchain.
|
||||
"""
|
||||
|
||||
# Decouple assets from block
|
||||
assets, block_dict = block.decouple_assets()
|
||||
metadatas, block_dict = block.decouple_metadata(block_dict)
|
||||
|
||||
# write the assets
|
||||
if assets:
|
||||
self.write_assets(assets)
|
||||
|
||||
if metadatas:
|
||||
self.write_metadata(metadatas)
|
||||
|
||||
# write the block
|
||||
return backend.query.store_block(self.connection, block_dict)
|
||||
|
||||
def get_assets(self, asset_ids):
|
||||
"""Return a list of assets that match the asset_ids
|
||||
|
||||
Args:
|
||||
asset_ids (:obj:`list` of :obj:`str`): A list of asset_ids to
|
||||
retrieve from the database.
|
||||
|
||||
Returns:
|
||||
list: The list of assets returned from the database.
|
||||
"""
|
||||
return backend.query.get_assets(self.connection, asset_ids)
|
||||
|
||||
def get_metadata(self, txn_ids):
|
||||
"""Return a list of metadata that match the transaction ids (txn_ids)
|
||||
|
||||
Args:
|
||||
txn_ids (:obj:`list` of :obj:`str`): A list of txn_ids to
|
||||
retrieve from the database.
|
||||
|
||||
Returns:
|
||||
list: The list of metadata returned from the database.
|
||||
"""
|
||||
return backend.query.get_metadata(self.connection, txn_ids)
|
||||
|
||||
def write_assets(self, assets):
|
||||
"""Writes a list of assets into the database.
|
||||
|
||||
Args:
|
||||
assets (:obj:`list` of :obj:`dict`): A list of assets to write to
|
||||
the database.
|
||||
"""
|
||||
return backend.query.store_assets(self.connection, assets)
|
||||
|
||||
def write_metadata(self, metadata):
|
||||
"""Writes a list of metadata into the database.
|
||||
|
||||
Args:
|
||||
metadata (:obj:`list` of :obj:`dict`): A list of metadata to write to
|
||||
the database.
|
||||
"""
|
||||
return backend.query.write_metadata(self.connection, metadata)
|
||||
|
||||
def text_search(self, search, *, limit=0, table='assets'):
|
||||
"""Return an iterator of assets that match the text search
|
||||
|
||||
Args:
|
||||
search (str): Text search string to query the text index
|
||||
limit (int, optional): Limit the number of returned documents.
|
||||
|
||||
Returns:
|
||||
iter: An iterator of assets that match the text search.
|
||||
"""
|
||||
objects = backend.query.text_search(self.connection, search, limit=limit,
|
||||
table=table)
|
||||
|
||||
# TODO: This is not efficient. There may be a more efficient way to
|
||||
# query by storing block ids with the assets and using fastquery.
|
||||
# See https://github.com/bigchaindb/bigchaindb/issues/1496
|
||||
for obj in objects:
|
||||
tx, status = self.get_transaction(obj['id'], True)
|
||||
if status == self.TX_VALID:
|
||||
yield obj
|
@ -14,7 +14,7 @@ class Transaction(Transaction):
|
||||
"""Validate transaction spend
|
||||
|
||||
Args:
|
||||
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
|
||||
bigchain (BigchainDB): an instantiated bigchaindb.tendermint.BigchainDB object.
|
||||
|
||||
Returns:
|
||||
The transaction (Transaction) if the transaction is valid else it
|
||||
@ -108,8 +108,8 @@ class Transaction(Transaction):
|
||||
asset from the asset table and reconstructs the transaction.
|
||||
|
||||
Args:
|
||||
bigchain (:class:`~bigchaindb.Bigchain`): An instance of Bigchain
|
||||
used to perform database queries.
|
||||
bigchain (:class:`~bigchaindb.tendermint.BigchainDB`): An instance
|
||||
of BigchainDB used to perform database queries.
|
||||
tx_dict_list (:list:`dict` or :obj:`dict`): The transaction dict or
|
||||
list of transaction dict as returned from the database.
|
||||
|
||||
|
@ -14,9 +14,9 @@ except ImportError:
|
||||
from sha3 import sha3_256
|
||||
|
||||
import requests
|
||||
|
||||
import bigchaindb
|
||||
from bigchaindb import backend, config_utils
|
||||
from bigchaindb import Bigchain
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.exceptions import (SchemaValidationError,
|
||||
ValidationError,
|
||||
@ -24,15 +24,45 @@ from bigchaindb.common.exceptions import (SchemaValidationError,
|
||||
from bigchaindb.tendermint.utils import encode_transaction, merkleroot
|
||||
from bigchaindb.tendermint import fastquery
|
||||
from bigchaindb import exceptions as core_exceptions
|
||||
from bigchaindb.consensus import BaseConsensusRules
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BigchainDB(Bigchain):
|
||||
class BigchainDB(object):
|
||||
"""Bigchain API
|
||||
|
||||
def __init__(self, connection=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Create, read, sign, write transactions to the database
|
||||
"""
|
||||
|
||||
BLOCK_INVALID = 'invalid'
|
||||
"""return if a block is invalid"""
|
||||
|
||||
BLOCK_VALID = TX_VALID = 'valid'
|
||||
"""return if a block is valid, or tx is in valid block"""
|
||||
|
||||
BLOCK_UNDECIDED = TX_UNDECIDED = 'undecided'
|
||||
"""return if block is undecided, or tx is in undecided block"""
|
||||
|
||||
TX_IN_BACKLOG = 'backlog'
|
||||
"""return if transaction is in backlog"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
"""Initialize the Bigchain instance
|
||||
|
||||
A Bigchain instance has several configuration parameters (e.g. host).
|
||||
If a parameter value is passed as an argument to the Bigchain
|
||||
__init__ method, then that is the value it will have.
|
||||
Otherwise, the parameter value will come from an environment variable.
|
||||
If that environment variable isn't set, then the value
|
||||
will come from the local configuration file. And if that variable
|
||||
isn't in the local configuration file, then the parameter will have
|
||||
its default value (defined in bigchaindb.__init__).
|
||||
|
||||
Args:
|
||||
connection (:class:`~bigchaindb.backend.connection.Connection`):
|
||||
A connection to the database.
|
||||
"""
|
||||
config_utils.autoconfigure()
|
||||
self.mode_list = ('broadcast_tx_async',
|
||||
'broadcast_tx_sync',
|
||||
@ -40,12 +70,20 @@ class BigchainDB(Bigchain):
|
||||
self.tendermint_host = bigchaindb.config['tendermint']['host']
|
||||
self.tendermint_port = bigchaindb.config['tendermint']['port']
|
||||
self.endpoint = 'http://{}:{}/'.format(self.tendermint_host, self.tendermint_port)
|
||||
|
||||
consensusPlugin = bigchaindb.config.get('consensus_plugin')
|
||||
|
||||
if consensusPlugin:
|
||||
self.consensus = config_utils.load_consensus_plugin(consensusPlugin)
|
||||
else:
|
||||
self.consensus = BaseConsensusRules
|
||||
|
||||
self.connection = connection if connection else backend.connect(**bigchaindb.config['database'])
|
||||
|
||||
def post_transaction(self, transaction, mode):
|
||||
"""Submit a valid transaction to the mempool."""
|
||||
if not mode or mode not in self.mode_list:
|
||||
raise ValidationError(('Mode must be one of the following {}.')
|
||||
raise ValidationError('Mode must be one of the following {}.'
|
||||
.format(', '.join(self.mode_list)))
|
||||
|
||||
payload = {
|
||||
@ -86,7 +124,7 @@ class BigchainDB(Bigchain):
|
||||
# else:
|
||||
# return (500, 'Error while validating the transaction')
|
||||
|
||||
def _process_status_code(self, status_code, failure_msg):
|
||||
def process_status_code(self, status_code, failure_msg):
|
||||
return (202, '') if status_code == 0 else (500, failure_msg)
|
||||
|
||||
def store_transaction(self, transaction):
|
||||
@ -237,6 +275,37 @@ class BigchainDB(Bigchain):
|
||||
else:
|
||||
return transaction
|
||||
|
||||
def get_transactions_filtered(self, asset_id, operation=None):
|
||||
"""Get a list of transactions filtered on some criteria
|
||||
"""
|
||||
txids = backend.query.get_txids_filtered(self.connection, asset_id,
|
||||
operation)
|
||||
for txid in txids:
|
||||
tx, status = self.get_transaction(txid, True)
|
||||
if status == self.TX_VALID:
|
||||
yield tx
|
||||
|
||||
def get_outputs_filtered(self, owner, spent=None):
|
||||
"""Get a list of output links filtered on some criteria
|
||||
|
||||
Args:
|
||||
owner (str): base58 encoded public_key.
|
||||
spent (bool): If ``True`` return only the spent outputs. If
|
||||
``False`` return only unspent outputs. If spent is
|
||||
not specified (``None``) return all outputs.
|
||||
|
||||
Returns:
|
||||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||
pointing to another transaction's condition
|
||||
"""
|
||||
outputs = self.fastquery.get_outputs_by_public_key(owner)
|
||||
if spent is None:
|
||||
return outputs
|
||||
elif spent is True:
|
||||
return self.fastquery.filter_unspent_outputs(outputs)
|
||||
elif spent is False:
|
||||
return self.fastquery.filter_spent_outputs(outputs)
|
||||
|
||||
def get_spent(self, txid, output, current_transactions=[]):
|
||||
transactions = backend.query.get_spent(self.connection, txid,
|
||||
output)
|
||||
@ -343,6 +412,51 @@ class BigchainDB(Bigchain):
|
||||
logger.warning('Invalid transaction (%s): %s', type(e).__name__, e)
|
||||
return False
|
||||
|
||||
def text_search(self, search, *, limit=0, table='assets'):
|
||||
"""Return an iterator of assets that match the text search
|
||||
|
||||
Args:
|
||||
search (str): Text search string to query the text index
|
||||
limit (int, optional): Limit the number of returned documents.
|
||||
|
||||
Returns:
|
||||
iter: An iterator of assets that match the text search.
|
||||
"""
|
||||
objects = backend.query.text_search(self.connection, search, limit=limit,
|
||||
table=table)
|
||||
|
||||
# TODO: This is not efficient. There may be a more efficient way to
|
||||
# query by storing block ids with the assets and using fastquery.
|
||||
# See https://github.com/bigchaindb/bigchaindb/issues/1496
|
||||
for obj in objects:
|
||||
tx, status = self.get_transaction(obj['id'], True)
|
||||
if status == self.TX_VALID:
|
||||
yield obj
|
||||
|
||||
def get_assets(self, asset_ids):
|
||||
"""Return a list of assets that match the asset_ids
|
||||
|
||||
Args:
|
||||
asset_ids (:obj:`list` of :obj:`str`): A list of asset_ids to
|
||||
retrieve from the database.
|
||||
|
||||
Returns:
|
||||
list: The list of assets returned from the database.
|
||||
"""
|
||||
return backend.query.get_assets(self.connection, asset_ids)
|
||||
|
||||
def get_metadata(self, txn_ids):
|
||||
"""Return a list of metadata that match the transaction ids (txn_ids)
|
||||
|
||||
Args:
|
||||
txn_ids (:obj:`list` of :obj:`str`): A list of txn_ids to
|
||||
retrieve from the database.
|
||||
|
||||
Returns:
|
||||
list: The list of metadata returned from the database.
|
||||
"""
|
||||
return backend.query.get_metadata(self.connection, txn_ids)
|
||||
|
||||
@property
|
||||
def fastquery(self):
|
||||
return fastquery.FastQuery(self.connection)
|
||||
|
@ -11,7 +11,7 @@ from flask_cors import CORS
|
||||
import gunicorn.app.base
|
||||
|
||||
from bigchaindb import utils
|
||||
from bigchaindb import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
from bigchaindb.web.routes import add_routes
|
||||
from bigchaindb.web.strip_content_type_middleware import StripContentTypeMiddleware
|
||||
|
||||
@ -67,7 +67,7 @@ def create_app(*, debug=False, threads=1, bigchaindb_factory=None):
|
||||
"""
|
||||
|
||||
if not bigchaindb_factory:
|
||||
bigchaindb_factory = Bigchain
|
||||
bigchaindb_factory = BigchainDB
|
||||
|
||||
app = Flask(__name__)
|
||||
app.wsgi_app = StripContentTypeMiddleware(app.wsgi_app)
|
||||
|
@ -5,7 +5,7 @@ import os
|
||||
import os.path
|
||||
|
||||
from bigchaindb.common.transaction import Transaction, Input, TransactionLink
|
||||
from bigchaindb.core import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
from bigchaindb.tendermint import lib
|
||||
from bigchaindb.web import server
|
||||
|
||||
|
@ -8,6 +8,7 @@ Tendermint Integration
|
||||
|
||||
.. automodule:: bigchaindb.tendermint.lib
|
||||
:special-members: __init__
|
||||
:noindex:
|
||||
|
||||
.. automodule:: bigchaindb.tendermint.core
|
||||
:special-members: __init__
|
||||
|
@ -90,7 +90,7 @@ def test_bigchain_run_init_when_db_exists(mocker, capsys):
|
||||
def test__run_init(mocker):
|
||||
from bigchaindb.commands.bigchaindb import _run_init
|
||||
bigchain_mock = mocker.patch(
|
||||
'bigchaindb.commands.bigchaindb.bigchaindb.tendermint.lib.BigchainDB')
|
||||
'bigchaindb.commands.bigchaindb.bigchaindb.tendermint.BigchainDB')
|
||||
init_db_mock = mocker.patch(
|
||||
'bigchaindb.commands.bigchaindb.schema.init_database',
|
||||
autospec=True,
|
||||
|
@ -280,7 +280,7 @@ class TestBigchainApi(object):
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_write_transaction(self, b, user_pk, user_sk):
|
||||
from bigchaindb import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
@ -294,7 +294,7 @@ class TestBigchainApi(object):
|
||||
tx_from_db, status = b.get_transaction(tx.id, include_status=True)
|
||||
|
||||
assert tx_from_db.to_dict() == tx.to_dict()
|
||||
assert status == Bigchain.TX_IN_BACKLOG
|
||||
assert status == BigchainDB.TX_IN_BACKLOG
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_read_transaction(self, b, user_pk, user_sk):
|
||||
@ -439,7 +439,7 @@ class TestBigchainApi(object):
|
||||
from bigchaindb.common.exceptions import InputDoesNotExist
|
||||
from bigchaindb.common.transaction import Input, TransactionLink
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
|
||||
# Create an input for a non existing transaction
|
||||
input = Input(Ed25519Sha256(public_key=b58decode(user_pk)),
|
||||
@ -449,7 +449,7 @@ class TestBigchainApi(object):
|
||||
asset_id='mock_asset_link')
|
||||
|
||||
with pytest.raises(InputDoesNotExist):
|
||||
tx.validate(Bigchain())
|
||||
tx.validate(BigchainDB())
|
||||
|
||||
def test_count_backlog(self, b, user_pk, alice):
|
||||
from bigchaindb.backend import query
|
||||
@ -970,9 +970,9 @@ class TestMultipleInputs(object):
|
||||
|
||||
|
||||
def test_get_owned_ids_calls_get_outputs_filtered():
|
||||
from bigchaindb.core import Bigchain
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||
b = Bigchain()
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
with patch('bigchaindb.tendermint.BigchainDB.get_outputs_filtered') as gof:
|
||||
b = BigchainDB()
|
||||
res = b.get_owned_ids('abc')
|
||||
gof.assert_called_once_with('abc', spent=False)
|
||||
assert res == gof()
|
||||
|
@ -35,6 +35,7 @@ def test_bigchain_instance_raises_when_not_configured(request, monkeypatch):
|
||||
import bigchaindb
|
||||
from bigchaindb import config_utils
|
||||
from bigchaindb.common import exceptions
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
assert 'CONFIGURED' not in bigchaindb.config
|
||||
|
||||
# We need to disable ``bigchaindb.config_utils.autoconfigure`` to avoid reading
|
||||
@ -42,7 +43,7 @@ def test_bigchain_instance_raises_when_not_configured(request, monkeypatch):
|
||||
monkeypatch.setattr(config_utils, 'autoconfigure', lambda: 0)
|
||||
|
||||
with pytest.raises(exceptions.ConfigurationError):
|
||||
bigchaindb.Bigchain()
|
||||
BigchainDB()
|
||||
|
||||
|
||||
def test_load_consensus_plugin_loads_default_rules_without_name():
|
||||
|
@ -26,10 +26,10 @@ def config(request, monkeypatch):
|
||||
|
||||
|
||||
def test_bigchain_class_default_initialization(config):
|
||||
from bigchaindb.core import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
from bigchaindb.consensus import BaseConsensusRules
|
||||
from bigchaindb.backend.connection import Connection
|
||||
bigchain = Bigchain()
|
||||
bigchain = BigchainDB()
|
||||
assert isinstance(bigchain.connection, Connection)
|
||||
assert bigchain.connection.host == config['database']['host']
|
||||
assert bigchain.connection.port == config['database']['port']
|
||||
@ -38,7 +38,7 @@ def test_bigchain_class_default_initialization(config):
|
||||
|
||||
|
||||
def test_bigchain_class_initialization_with_parameters(config):
|
||||
from bigchaindb.core import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
from bigchaindb.backend import connect
|
||||
from bigchaindb.consensus import BaseConsensusRules
|
||||
init_db_kwargs = {
|
||||
@ -48,7 +48,7 @@ def test_bigchain_class_initialization_with_parameters(config):
|
||||
'name': 'this_is_the_db_name',
|
||||
}
|
||||
connection = connect(**init_db_kwargs)
|
||||
bigchain = Bigchain(connection=connection, **init_db_kwargs)
|
||||
bigchain = BigchainDB(connection=connection, **init_db_kwargs)
|
||||
assert bigchain.connection == connection
|
||||
assert bigchain.connection.host == init_db_kwargs['host']
|
||||
assert bigchain.connection.port == init_db_kwargs['port']
|
||||
@ -58,13 +58,13 @@ def test_bigchain_class_initialization_with_parameters(config):
|
||||
|
||||
def test_get_blocks_status_containing_tx(monkeypatch):
|
||||
from bigchaindb.backend import query as backend_query
|
||||
from bigchaindb.core import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
blocks = [
|
||||
{'id': 1}, {'id': 2}
|
||||
]
|
||||
monkeypatch.setattr(backend_query, 'get_blocks_status_from_transaction', lambda x: blocks)
|
||||
monkeypatch.setattr(Bigchain, 'block_election_status', lambda x, y, z: Bigchain.BLOCK_VALID)
|
||||
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
|
||||
monkeypatch.setattr(BigchainDB, 'block_election_status', lambda x, y, z: BigchainDB.BLOCK_VALID)
|
||||
bigchain = BigchainDB(public_key='pubkey', private_key='privkey')
|
||||
with pytest.raises(Exception):
|
||||
bigchain.get_blocks_status_containing_tx('txid')
|
||||
|
||||
|
@ -372,7 +372,7 @@ def test_transactions_get_list_good(client):
|
||||
|
||||
asset_id = '1' * 64
|
||||
|
||||
with patch('bigchaindb.core.Bigchain.get_transactions_filtered', get_txs_patched):
|
||||
with patch('bigchaindb.tendermint.BigchainDB.get_transactions_filtered', get_txs_patched):
|
||||
url = TX_ENDPOINT + '?asset_id=' + asset_id
|
||||
assert client.get(url).json == [
|
||||
['asset_id', asset_id],
|
||||
@ -389,7 +389,7 @@ def test_transactions_get_list_good(client):
|
||||
def test_transactions_get_list_bad(client):
|
||||
def should_not_be_called():
|
||||
assert False
|
||||
with patch('bigchaindb.core.Bigchain.get_transactions_filtered',
|
||||
with patch('bigchaindb.tendermint.BigchainDB.get_transactions_filtered',
|
||||
lambda *_, **__: should_not_be_called()):
|
||||
# Test asset id validated
|
||||
url = TX_ENDPOINT + '?asset_id=' + '1' * 63
|
||||
@ -404,7 +404,7 @@ def test_transactions_get_list_bad(client):
|
||||
|
||||
@pytest.mark.tendermint
|
||||
def test_return_only_valid_transaction(client):
|
||||
from bigchaindb import Bigchain
|
||||
from bigchaindb.tendermint import BigchainDB
|
||||
|
||||
def get_transaction_patched(status):
|
||||
def inner(self, tx_id, include_status):
|
||||
@ -415,13 +415,13 @@ def test_return_only_valid_transaction(client):
|
||||
# UNDECIDED or VALID block, as well as transactions from the backlog.
|
||||
# As the endpoint uses `get_transaction`, we don't have to test
|
||||
# against invalid transactions here.
|
||||
with patch('bigchaindb.core.Bigchain.get_transaction',
|
||||
get_transaction_patched(Bigchain.TX_UNDECIDED)):
|
||||
with patch('bigchaindb.tendermint.BigchainDB.get_transaction',
|
||||
get_transaction_patched(BigchainDB.TX_UNDECIDED)):
|
||||
url = '{}{}'.format(TX_ENDPOINT, '123')
|
||||
assert client.get(url).status_code == 404
|
||||
|
||||
with patch('bigchaindb.core.Bigchain.get_transaction',
|
||||
get_transaction_patched(Bigchain.TX_IN_BACKLOG)):
|
||||
with patch('bigchaindb.tendermint.BigchainDB.get_transaction',
|
||||
get_transaction_patched(BigchainDB.TX_IN_BACKLOG)):
|
||||
url = '{}{}'.format(TX_ENDPOINT, '123')
|
||||
assert client.get(url).status_code == 404
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user