mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-06-17 18:13:22 +02:00
Merge remote-tracking branch 'origin/master' into feat/1055/commands-add-remove-replicas
This commit is contained in:
commit
c9af57943b
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -16,6 +16,27 @@ For reference, the possible headings are:
|
|||
* **Notes**
|
||||
|
||||
|
||||
## [0.8.2] - 2017-01-27
|
||||
Tag name: v0.8.2
|
||||
|
||||
### Fixed
|
||||
- Fix spend input twice in same transaction
|
||||
(https://github.com/bigchaindb/bigchaindb/issues/1099).
|
||||
|
||||
|
||||
## [0.8.1] - 2017-01-16
|
||||
Tag name: v0.8.1
|
||||
= commit:
|
||||
committed:
|
||||
|
||||
### Changed
|
||||
- Upgrade pysha3 to 1.0.0 (supports official NIST standard).
|
||||
|
||||
### Fixed
|
||||
- Workaround for rapidjson problem with package metadata extraction
|
||||
(https://github.com/kenrobbins/python-rapidjson/pull/52).
|
||||
|
||||
|
||||
## [0.8.0] - 2016-11-29
|
||||
Tag name: v0.8.0
|
||||
= commit:
|
||||
|
|
11
Makefile
11
Makefile
|
@ -65,12 +65,11 @@ coverage: ## check code coverage quickly with the default Python
|
|||
$(BROWSER) htmlcov/index.html
|
||||
|
||||
docs: ## generate Sphinx HTML documentation, including API docs
|
||||
rm -f docs/bigchaindb.rst
|
||||
rm -f docs/modules.rst
|
||||
sphinx-apidoc -o docs/ bigchaindb
|
||||
$(MAKE) -C docs clean
|
||||
$(MAKE) -C docs html
|
||||
$(BROWSER) docs/_build/html/index.html
|
||||
$(MAKE) -C docs/root clean
|
||||
$(MAKE) -C docs/root html
|
||||
$(MAKE) -C docs/server clean
|
||||
$(MAKE) -C docs/server html
|
||||
$(BROWSER) docs/root/_build/html/index.html
|
||||
|
||||
servedocs: docs ## compile the docs watching for changes
|
||||
watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
|
||||
|
|
|
@ -1,23 +1,52 @@
|
|||
# Our Release Process
|
||||
|
||||
This is a summary of the steps we go through to release a new version of BigchainDB Server.
|
||||
The release process for BigchainDB server differs slightly depending on whether it's a minor or a patch release.
|
||||
|
||||
1. Update the `CHANGELOG.md` file
|
||||
1. Update the version numbers in `bigchaindb/version.py`. Note that we try to use [semantic versioning](http://semver.org/) (i.e. MAJOR.MINOR.PATCH)
|
||||
1. Go to the [bigchaindb/bigchaindb Releases page on GitHub](https://github.com/bigchaindb/bigchaindb/releases)
|
||||
and click the "Draft a new release" button
|
||||
1. Name the tag something like v0.7.0
|
||||
1. The target should be a specific commit: the one when the update of `bigchaindb/version.py` got merged into master
|
||||
1. The release title should be something like v0.7.0
|
||||
1. The description should be copied from the `CHANGELOG.md` file updated above
|
||||
1. Generate and send the latest `bigchaindb` package to PyPI. Dimi and Sylvain can do this, maybe others
|
||||
1. Login to readthedocs.org as a maintainer of the BigchainDB Server docs.
|
||||
Go to Admin --> Versions and under **Choose Active Versions**, make sure that the new version's tag is
|
||||
"Active" and "Public"
|
||||
BigchainDB follows [semantic versioning](http://semver.org/) (i.e. MAJOR.MINOR.PATCH), taking into account
|
||||
that [major version 0.x does not export a stable API](http://semver.org/#spec-item-4).
|
||||
|
||||
After the release:
|
||||
## Minor release
|
||||
|
||||
1. Update `bigchaindb/version.py` again, to be something like 0.8.0.dev (with a dev on the end).
|
||||
A minor release is preceeded by a feature freeze and created from the 'master' branch. This is a summary of the steps we go through to release a new minor version of BigchainDB Server.
|
||||
|
||||
1. Update the `CHANGELOG.md` file in master
|
||||
1. Create and checkout a new branch for the release, named after the minor version, without preceeding 'v', ie: `git checkout -b 0.9`
|
||||
1. Commit changes and push new branch to Github
|
||||
1. Follow steps outlined in [Common Steps](#common-steps)
|
||||
1. In 'master' branch, Edit `bigchaindb/version.py`, increment the minor version to the next planned release ie: `0.10.0.dev'.
|
||||
This is so people reading the latest docs will know that they're for the latest (master branch)
|
||||
version of BigchainDB Server, not the docs at the time of the most recent release (which are also
|
||||
available).
|
||||
|
||||
Congratulations, you have released BigchainDB!
|
||||
|
||||
## Patch release
|
||||
|
||||
A patch release is similar to a minor release, but piggybacks on an existing minor release branch:
|
||||
|
||||
1. Check out the minor release branch
|
||||
1. Apply the changes you want, ie using `git cherry-pick`.
|
||||
1. Update the `CHANGELOG.md` file
|
||||
1. Increment the patch version in `bigchaindb/version.py`, ie: "0.9.1"
|
||||
1. Follow steps outlined in [Common Steps](#common-steps)
|
||||
|
||||
## Common steps
|
||||
|
||||
These steps are common between minor and patch releases:
|
||||
|
||||
1. Go to the [bigchaindb/bigchaindb Releases page on GitHub](https://github.com/bigchaindb/bigchaindb/releases)
|
||||
and click the "Draft a new release" button
|
||||
1. Fill in the details:
|
||||
- Tag version: version number preceeded by 'v', ie: "v0.9.1"
|
||||
- Target: the release branch that was just pushed
|
||||
- Title: Same as tag name
|
||||
- Description: The body of the changelog entry (Added, Changed etc)
|
||||
1. Publish the release on Github
|
||||
1. Generate the release tarball with `python setup.py sdist`. Upload the release to Pypi.
|
||||
1. Login to readthedocs.org as a maintainer of the BigchainDB Server docs.
|
||||
Go to Admin --> Versions and under **Choose Active Versions**, make sure that the new version's tag is
|
||||
"Active" and "Public", and make sure the new version's branch
|
||||
(without the 'v' in front) is _not_ active
|
||||
1. Also in readthedocs.org, go to Admin --> Advanced Settings
|
||||
and make sure that "Default branch:" (i.e. what "latest" points to)
|
||||
is set to the new release's tag, e.g. `v0.9.1`. (Don't miss the 'v' in front.)
|
||||
|
|
|
@ -5,12 +5,6 @@ import os
|
|||
# PORT_NUMBER = reduce(lambda x, y: x * y, map(ord, 'BigchainDB')) % 2**16
|
||||
# basically, the port number is 9984
|
||||
|
||||
|
||||
def _get_database_from_env():
|
||||
return globals()['_database_' + os.environ.get(
|
||||
'BIGCHAINDB_DATABASE_BACKEND', 'rethinkdb')]
|
||||
|
||||
|
||||
_database_rethinkdb = {
|
||||
'backend': os.environ.get('BIGCHAINDB_DATABASE_BACKEND', 'rethinkdb'),
|
||||
'host': os.environ.get('BIGCHAINDB_DATABASE_HOST', 'localhost'),
|
||||
|
@ -26,6 +20,11 @@ _database_mongodb = {
|
|||
'replicaset': os.environ.get('BIGCHAINDB_DATABASE_REPLICASET', 'bigchain-rs'),
|
||||
}
|
||||
|
||||
_database_map = {
|
||||
'mongodb': _database_mongodb,
|
||||
'rethinkdb': _database_rethinkdb
|
||||
}
|
||||
|
||||
config = {
|
||||
'server': {
|
||||
# Note: this section supports all the Gunicorn settings:
|
||||
|
@ -34,7 +33,9 @@ config = {
|
|||
'workers': None, # if none, the value will be cpu_count * 2 + 1
|
||||
'threads': None, # if none, the value will be cpu_count * 2 + 1
|
||||
},
|
||||
'database': _get_database_from_env(),
|
||||
'database': _database_map[
|
||||
os.environ.get('BIGCHAINDB_DATABASE_BACKEND', 'rethinkdb')
|
||||
],
|
||||
'keypair': {
|
||||
'public': None,
|
||||
'private': None,
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
"""Query implementation for MongoDB"""
|
||||
|
||||
from time import time
|
||||
from itertools import chain
|
||||
|
||||
from pymongo import ReturnDocument
|
||||
from pymongo import errors
|
||||
|
||||
from bigchaindb import backend
|
||||
from bigchaindb.common.exceptions import CyclicBlockchainError
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.backend.utils import module_dispatch_registrar
|
||||
from bigchaindb.backend.mongodb.connection import MongoDBConnection
|
||||
|
||||
|
@ -83,18 +85,40 @@ def get_blocks_status_from_transaction(conn, transaction_id):
|
|||
|
||||
|
||||
@register_query(MongoDBConnection)
|
||||
def get_txids_by_asset_id(conn, asset_id):
|
||||
cursor = conn.db['bigchain'].aggregate([
|
||||
{'$match': {
|
||||
'block.transactions.asset.id': asset_id
|
||||
}},
|
||||
{'$unwind': '$block.transactions'},
|
||||
{'$match': {
|
||||
'block.transactions.asset.id': asset_id
|
||||
}},
|
||||
{'$project': {'block.transactions.id': True}}
|
||||
])
|
||||
return (elem['block']['transactions']['id'] for elem in cursor)
|
||||
def get_txids_filtered(conn, asset_id, operation=None):
|
||||
parts = []
|
||||
|
||||
if operation in (Transaction.CREATE, None):
|
||||
# get the txid of the create transaction for asset_id
|
||||
cursor = conn.db['bigchain'].aggregate([
|
||||
{'$match': {
|
||||
'block.transactions.id': asset_id,
|
||||
'block.transactions.operation': 'CREATE'
|
||||
}},
|
||||
{'$unwind': '$block.transactions'},
|
||||
{'$match': {
|
||||
'block.transactions.id': asset_id,
|
||||
'block.transactions.operation': 'CREATE'
|
||||
}},
|
||||
{'$project': {'block.transactions.id': True}}
|
||||
])
|
||||
parts.append(elem['block']['transactions']['id'] for elem in cursor)
|
||||
|
||||
if operation in (Transaction.TRANSFER, None):
|
||||
# get txids of transfer transaction with asset_id
|
||||
cursor = conn.db['bigchain'].aggregate([
|
||||
{'$match': {
|
||||
'block.transactions.asset.id': asset_id
|
||||
}},
|
||||
{'$unwind': '$block.transactions'},
|
||||
{'$match': {
|
||||
'block.transactions.asset.id': asset_id
|
||||
}},
|
||||
{'$project': {'block.transactions.id': True}}
|
||||
])
|
||||
parts.append(elem['block']['transactions']['id'] for elem in cursor)
|
||||
|
||||
return chain(*parts)
|
||||
|
||||
|
||||
@register_query(MongoDBConnection)
|
||||
|
|
|
@ -107,25 +107,6 @@ def get_blocks_status_from_transaction(connection, transaction_id):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
@singledispatch
|
||||
def get_txids_by_asset_id(connection, asset_id):
|
||||
"""Retrieves transactions ids related to a particular asset.
|
||||
|
||||
A digital asset in bigchaindb is identified by its ``CREATE``
|
||||
transaction's ID. Knowing this ID allows us to query all the
|
||||
transactions related to a particular digital asset.
|
||||
|
||||
Args:
|
||||
asset_id (str): the ID of the asset.
|
||||
|
||||
Returns:
|
||||
A list of transactions ids related to the asset. If no transaction
|
||||
exists for that asset it returns an empty list ``[]``
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@singledispatch
|
||||
def get_asset_by_id(conneciton, asset_id):
|
||||
"""Returns the asset associated with an asset_id.
|
||||
|
@ -318,3 +299,16 @@ def get_unvoted_blocks(connection, node_pubkey):
|
|||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@singledispatch
|
||||
def get_txids_filtered(connection, asset_id, operation=None):
|
||||
"""
|
||||
Return all transactions for a particular asset id and optional operation.
|
||||
|
||||
Args:
|
||||
asset_id (str): ID of transaction that defined the asset
|
||||
operation (str) (optional): Operation to filter on
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -77,3 +77,5 @@ class RethinkDBConnection(Connection):
|
|||
wait_time = 2**i
|
||||
logging.debug('Error connecting to database, waiting %ss', wait_time)
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
break
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from itertools import chain
|
||||
from time import time
|
||||
|
||||
import rethinkdb as r
|
||||
|
||||
from bigchaindb import backend, utils
|
||||
from bigchaindb.common import exceptions
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.backend.utils import module_dispatch_registrar
|
||||
from bigchaindb.backend.rethinkdb.connection import RethinkDBConnection
|
||||
|
||||
|
@ -72,19 +74,27 @@ def get_blocks_status_from_transaction(connection, transaction_id):
|
|||
|
||||
|
||||
@register_query(RethinkDBConnection)
|
||||
def get_txids_by_asset_id(connection, asset_id):
|
||||
def get_txids_filtered(connection, asset_id, operation=None):
|
||||
# here we only want to return the transaction ids since later on when
|
||||
# we are going to retrieve the transaction with status validation
|
||||
|
||||
# Then find any TRANSFER transactions related to the asset
|
||||
tx_cursor = connection.run(
|
||||
r.table('bigchain')
|
||||
.get_all(asset_id, index='asset_id')
|
||||
.concat_map(lambda block: block['block']['transactions'])
|
||||
.filter(lambda transaction: transaction['asset']['id'] == asset_id)
|
||||
.get_field('id'))
|
||||
parts = []
|
||||
|
||||
return tx_cursor
|
||||
if operation in (Transaction.CREATE, None):
|
||||
# First find the asset's CREATE transaction
|
||||
parts.append(connection.run(
|
||||
_get_asset_create_tx_query(asset_id).get_field('id')))
|
||||
|
||||
if operation in (Transaction.TRANSFER, None):
|
||||
# Then find any TRANSFER transactions related to the asset
|
||||
parts.append(connection.run(
|
||||
r.table('bigchain')
|
||||
.get_all(asset_id, index='asset_id')
|
||||
.concat_map(lambda block: block['block']['transactions'])
|
||||
.filter(lambda transaction: transaction['asset']['id'] == asset_id)
|
||||
.get_field('id')))
|
||||
|
||||
return chain(*parts)
|
||||
|
||||
|
||||
@register_query(RethinkDBConnection)
|
||||
|
|
|
@ -12,9 +12,9 @@ def module_dispatch_registrar(module):
|
|||
return dispatch_registrar.register(obj_type)(func)
|
||||
except AttributeError as ex:
|
||||
raise ModuleDispatchRegistrationError(
|
||||
("`{module}` does not contain a single-dispatchable "
|
||||
"function named `{func}`. The module being registered "
|
||||
"was not implemented correctly!").format(
|
||||
('`{module}` does not contain a single-dispatchable '
|
||||
'function named `{func}`. The module being registered '
|
||||
'was not implemented correctly!').format(
|
||||
func=func_name, module=module.__name__)) from ex
|
||||
return wrapper
|
||||
return dispatch_wrapper
|
||||
|
|
|
@ -90,12 +90,7 @@ def run_configure(args, skip_if_exists=False):
|
|||
# select the correct config defaults based on the backend
|
||||
print('Generating default configuration for backend {}'
|
||||
.format(args.backend))
|
||||
database = {}
|
||||
if args.backend == 'rethinkdb':
|
||||
database = bigchaindb._database_rethinkdb
|
||||
elif args.backend == 'mongodb':
|
||||
database = bigchaindb._database_mongodb
|
||||
conf['database'] = database
|
||||
conf['database'] = bigchaindb._database_map[args.backend]
|
||||
|
||||
if not args.yes:
|
||||
for key in ('bind', ):
|
||||
|
|
|
@ -103,8 +103,8 @@ definitions:
|
|||
description: |
|
||||
Description of the asset being transacted. In the case of a ``TRANSFER``
|
||||
transaction, this field contains only the ID of asset. In the case
|
||||
of a ``CREATE`` transaction, this field contains the user-defined
|
||||
payload and the asset ID (duplicated from the Transaction ID).
|
||||
of a ``CREATE`` transaction, this field contains only the user-defined
|
||||
payload.
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
|
|
|
@ -159,7 +159,7 @@ class TransactionLink(object):
|
|||
|
||||
def __eq__(self, other):
|
||||
# TODO: If `other !== TransactionLink` return `False`
|
||||
return self.to_dict() == self.to_dict()
|
||||
return self.to_dict() == other.to_dict()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, link):
|
||||
|
@ -410,7 +410,7 @@ class Transaction(object):
|
|||
TRANSFER = 'TRANSFER'
|
||||
GENESIS = 'GENESIS'
|
||||
ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS)
|
||||
VERSION = bigchaindb.version.__version__
|
||||
VERSION = bigchaindb.version.__short_version__[:-4] # 0.9, 0.10 etc
|
||||
|
||||
def __init__(self, operation, asset, inputs=None, outputs=None,
|
||||
metadata=None, version=None):
|
||||
|
@ -444,7 +444,6 @@ class Transaction(object):
|
|||
asset is not None and not (isinstance(asset, dict) and 'data' in asset)):
|
||||
raise TypeError(('`asset` must be None or a dict holding a `data` '
|
||||
" property instance for '{}' Transactions".format(operation)))
|
||||
asset.pop('id', None) # Remove duplicated asset ID if there is one
|
||||
elif (operation == Transaction.TRANSFER and
|
||||
not (isinstance(asset, dict) and 'id' in asset)):
|
||||
raise TypeError(('`asset` must be a dict holding an `id` property '
|
||||
|
@ -927,11 +926,9 @@ class Transaction(object):
|
|||
|
||||
tx_no_signatures = Transaction._remove_signatures(tx)
|
||||
tx_serialized = Transaction._to_str(tx_no_signatures)
|
||||
tx['id'] = Transaction._to_hash(tx_serialized)
|
||||
if self.operation == Transaction.CREATE:
|
||||
# Duplicate asset into asset for consistency with TRANSFER
|
||||
# transactions
|
||||
tx['asset']['id'] = tx['id']
|
||||
tx_id = Transaction._to_hash(tx_serialized)
|
||||
|
||||
tx['id'] = tx_id
|
||||
return tx
|
||||
|
||||
@staticmethod
|
||||
|
@ -955,9 +952,6 @@ class Transaction(object):
|
|||
# case could yield incorrect signatures. This is why we only
|
||||
# set it to `None` if it's set in the dict.
|
||||
input_['fulfillment'] = None
|
||||
# Pop duplicated asset_id from CREATE tx
|
||||
if tx_dict['operation'] == Transaction.CREATE:
|
||||
tx_dict['asset'].pop('id', None)
|
||||
return tx_dict
|
||||
|
||||
@staticmethod
|
||||
|
@ -1037,10 +1031,6 @@ class Transaction(object):
|
|||
"the hash of its body, i.e. it's not valid.")
|
||||
raise InvalidHash(err_msg.format(proposed_tx_id))
|
||||
|
||||
if tx_body.get('operation') == Transaction.CREATE:
|
||||
if proposed_tx_id != tx_body['asset'].get('id'):
|
||||
raise InvalidHash("CREATE tx has wrong asset_id")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, tx):
|
||||
"""Transforms a Python dictionary to a Transaction object.
|
||||
|
|
|
@ -39,6 +39,6 @@ class BaseConsensusRules():
|
|||
except SchemaValidationError as exc:
|
||||
logger.warning(exc)
|
||||
else:
|
||||
logger.warning("Vote failed signature verification: "
|
||||
"%s with voters: %s", signed_vote, voters)
|
||||
logger.warning('Vote failed signature verification: '
|
||||
'%s with voters: %s', signed_vote, voters)
|
||||
return False
|
||||
|
|
|
@ -317,30 +317,6 @@ class Bigchain(object):
|
|||
else:
|
||||
return None
|
||||
|
||||
def get_transactions_by_asset_id(self, asset_id):
|
||||
"""Retrieves valid or undecided 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 asset.
|
||||
|
||||
Returns:
|
||||
A list of valid or undecided transactions related to the asset.
|
||||
If no transaction exists for that asset it returns an empty list
|
||||
`[]`
|
||||
"""
|
||||
txids = backend.query.get_txids_by_asset_id(self.connection, asset_id)
|
||||
transactions = []
|
||||
for txid in txids:
|
||||
tx = self.get_transaction(txid)
|
||||
if tx:
|
||||
transactions.append(tx)
|
||||
return transactions
|
||||
|
||||
def get_asset_by_id(self, asset_id):
|
||||
"""Returns the asset associated with an asset_id.
|
||||
|
||||
|
@ -397,8 +373,9 @@ class Bigchain(object):
|
|||
else:
|
||||
return None
|
||||
|
||||
def get_owned_ids(self, owner):
|
||||
"""Retrieve a list of ``txid`` s that can be used as inputs.
|
||||
def get_outputs(self, owner):
|
||||
"""Retrieve a list of links to transaction outputs for a given public
|
||||
key.
|
||||
|
||||
Args:
|
||||
owner (str): base58 encoded public key.
|
||||
|
@ -407,10 +384,9 @@ class Bigchain(object):
|
|||
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
|
||||
pointing to another transaction's condition
|
||||
"""
|
||||
|
||||
# get all transactions in which owner is in the `owners_after` list
|
||||
response = backend.query.get_owned_ids(self.connection, owner)
|
||||
owned = []
|
||||
links = []
|
||||
|
||||
for tx in response:
|
||||
# disregard transactions from invalid blocks
|
||||
|
@ -435,11 +411,41 @@ class Bigchain(object):
|
|||
# subfulfillment for `owner`
|
||||
if utils.condition_details_has_owner(output['condition']['details'], owner):
|
||||
tx_link = TransactionLink(tx['id'], index)
|
||||
# check if input was already spent
|
||||
if not self.get_spent(tx_link.txid, tx_link.output):
|
||||
owned.append(tx_link)
|
||||
links.append(tx_link)
|
||||
return links
|
||||
|
||||
return owned
|
||||
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, include_spent=False)
|
||||
|
||||
def get_outputs_filtered(self, owner, include_spent=True):
|
||||
"""
|
||||
Get a list of output links filtered on some criteria
|
||||
"""
|
||||
outputs = self.get_outputs(owner)
|
||||
if not include_spent:
|
||||
outputs = [o for o in outputs
|
||||
if not self.get_spent(o.txid, o.output)]
|
||||
return 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
|
||||
|
||||
def create_block(self, validated_transactions):
|
||||
"""Creates a block given a list of `validated_transactions`.
|
||||
|
|
|
@ -88,6 +88,11 @@ class Transaction(Transaction):
|
|||
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']:
|
||||
|
|
|
@ -5,7 +5,7 @@ from bigchaindb.web.views import (
|
|||
info,
|
||||
statuses,
|
||||
transactions as tx,
|
||||
unspents,
|
||||
outputs,
|
||||
votes,
|
||||
)
|
||||
|
||||
|
@ -30,7 +30,7 @@ ROUTES_API_V1 = [
|
|||
r('statuses/', statuses.StatusApi),
|
||||
r('transactions/<string:tx_id>', tx.TransactionApi),
|
||||
r('transactions', tx.TransactionListApi),
|
||||
r('unspents/', unspents.UnspentListApi),
|
||||
r('outputs/', outputs.OutputListApi),
|
||||
r('votes/', votes.VotesApi),
|
||||
]
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ class ApiV1Index(Resource):
|
|||
'/drivers-clients/http-client-server-api.html',
|
||||
]
|
||||
return {
|
||||
"_links": {
|
||||
"docs": ''.join(docs_url),
|
||||
"self": api_root,
|
||||
"statuses": api_root + "statuses/",
|
||||
"transactions": api_root + "transactions/",
|
||||
'_links': {
|
||||
'docs': ''.join(docs_url),
|
||||
'self': api_root,
|
||||
'statuses': api_root + 'statuses/',
|
||||
'transactions': api_root + 'transactions/',
|
||||
},
|
||||
}
|
||||
|
|
28
bigchaindb/web/views/outputs.py
Normal file
28
bigchaindb/web/views/outputs.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from flask import current_app
|
||||
from flask_restful import reqparse, Resource
|
||||
|
||||
from bigchaindb.web.views import parameters
|
||||
|
||||
|
||||
class OutputListApi(Resource):
|
||||
def get(self):
|
||||
"""API endpoint to retrieve a list of links to transaction
|
||||
outputs.
|
||||
|
||||
Returns:
|
||||
A :obj:`list` of :cls:`str` of links to outputs.
|
||||
"""
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('public_key', type=parameters.valid_ed25519,
|
||||
required=True)
|
||||
parser.add_argument('unspent', type=parameters.valid_bool)
|
||||
args = parser.parse_args()
|
||||
|
||||
pool = current_app.config['bigchain_pool']
|
||||
include_spent = not args['unspent']
|
||||
|
||||
with pool() as bigchain:
|
||||
outputs = bigchain.get_outputs_filtered(args['public_key'],
|
||||
include_spent)
|
||||
# NOTE: We pass '..' as a path to create a valid relative URI
|
||||
return [u.to_uri('..') for u in outputs]
|
32
bigchaindb/web/views/parameters.py
Normal file
32
bigchaindb/web/views/parameters.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import re
|
||||
|
||||
|
||||
def valid_txid(txid):
|
||||
if re.match('^[a-fA-F0-9]{64}$', txid):
|
||||
return txid.lower()
|
||||
raise ValueError("Invalid hash")
|
||||
|
||||
|
||||
def valid_bool(val):
|
||||
val = val.lower()
|
||||
if val == 'true':
|
||||
return True
|
||||
if val == 'false':
|
||||
return False
|
||||
raise ValueError('Boolean value must be "true" or "false" (lowercase)')
|
||||
|
||||
|
||||
def valid_ed25519(key):
|
||||
if (re.match('^[1-9a-zA-Z]{43,44}$', key) and not
|
||||
re.match('.*[Il0O]', key)):
|
||||
return key
|
||||
raise ValueError("Invalid base58 ed25519 key")
|
||||
|
||||
|
||||
def valid_operation(op):
|
||||
op = op.upper()
|
||||
if op == 'CREATE':
|
||||
return 'CREATE'
|
||||
if op == 'TRANSFER':
|
||||
return 'TRANSFER'
|
||||
raise ValueError('Operation must be "CREATE" or "TRANSFER')
|
|
@ -28,7 +28,7 @@ class StatusApi(Resource):
|
|||
|
||||
# logical xor - exactly one query argument required
|
||||
if bool(tx_id) == bool(block_id):
|
||||
return make_error(400, "Provide exactly one query parameter. Choices are: block_id, tx_id")
|
||||
return make_error(400, 'Provide exactly one query parameter. Choices are: block_id, tx_id')
|
||||
|
||||
pool = current_app.config['bigchain_pool']
|
||||
status, links = None, None
|
||||
|
@ -37,7 +37,7 @@ class StatusApi(Resource):
|
|||
if tx_id:
|
||||
status = bigchain.get_status(tx_id)
|
||||
links = {
|
||||
"tx": "/transactions/{}".format(tx_id)
|
||||
'tx': '/transactions/{}'.format(tx_id)
|
||||
}
|
||||
|
||||
elif block_id:
|
||||
|
@ -56,7 +56,7 @@ class StatusApi(Resource):
|
|||
|
||||
if links:
|
||||
response.update({
|
||||
"_links": links
|
||||
'_links': links
|
||||
})
|
||||
|
||||
return response
|
||||
|
|
|
@ -7,7 +7,8 @@ For more information please refer to the documentation on ReadTheDocs:
|
|||
import logging
|
||||
|
||||
from flask import current_app, request
|
||||
from flask_restful import Resource
|
||||
from flask_restful import Resource, reqparse
|
||||
|
||||
|
||||
from bigchaindb.common.exceptions import (
|
||||
AmountError,
|
||||
|
@ -25,6 +26,7 @@ from bigchaindb.common.exceptions import (
|
|||
import bigchaindb
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.web.views.base import make_error
|
||||
from bigchaindb.web.views import parameters
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -51,6 +53,18 @@ class TransactionApi(Resource):
|
|||
|
||||
|
||||
class TransactionListApi(Resource):
|
||||
def get(self):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('operation', type=parameters.valid_operation)
|
||||
parser.add_argument('asset_id', type=parameters.valid_txid,
|
||||
required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
with current_app.config['bigchain_pool']() as bigchain:
|
||||
txs = bigchain.get_transactions_filtered(**args)
|
||||
|
||||
return [tx.to_dict() for tx in txs]
|
||||
|
||||
def post(self):
|
||||
"""API endpoint to push transactions to the Federation.
|
||||
|
||||
|
@ -99,4 +113,4 @@ class TransactionListApi(Resource):
|
|||
with monitor.timer('write_transaction', rate=rate):
|
||||
bigchain.write_transaction(tx_obj)
|
||||
|
||||
return tx
|
||||
return tx, 202
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
from flask import current_app
|
||||
from flask_restful import reqparse, Resource
|
||||
|
||||
|
||||
class UnspentListApi(Resource):
|
||||
def get(self):
|
||||
"""API endpoint to retrieve a list of links to transactions's
|
||||
conditions that have not been used in any previous transaction.
|
||||
|
||||
Returns:
|
||||
A :obj:`list` of :cls:`str` of links to unfulfilled conditions.
|
||||
"""
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('public_key', type=str, location='args',
|
||||
required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
pool = current_app.config['bigchain_pool']
|
||||
|
||||
with pool() as bigchain:
|
||||
unspents = bigchain.get_owned_ids(args['public_key'])
|
||||
# NOTE: We pass '..' as a path to create a valid relative URI
|
||||
return [u.to_uri('..') for u in unspents]
|
|
@ -1,8 +1,10 @@
|
|||
#! /bin/bash
|
||||
#!/bin/bash
|
||||
|
||||
# The set -e option instructs bash to immediately exit
|
||||
# if any command has a non-zero exit status
|
||||
set -e
|
||||
set -euo pipefail
|
||||
# -e Abort at the first failed line (i.e. if exit status is not 0)
|
||||
# -u Abort when undefined variable is used
|
||||
# -o pipefail (Bash-only) Piped commands return the status
|
||||
# of the last failed command, rather than the status of the last command
|
||||
|
||||
# Check for the first command-line argument
|
||||
# (the name of the AWS deployment config file)
|
||||
|
|
|
@ -5,7 +5,7 @@ services:
|
|||
image: mongo:3.4.1
|
||||
ports:
|
||||
- "27017"
|
||||
command: mongod --replSet=rs0
|
||||
command: mongod --replSet=bigchain-rs
|
||||
|
||||
rdb:
|
||||
image: rethinkdb
|
||||
|
|
|
@ -4,13 +4,61 @@ import json
|
|||
import os
|
||||
import os.path
|
||||
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.transaction import Transaction, Input, TransactionLink
|
||||
from bigchaindb.core import Bigchain
|
||||
from bigchaindb.models import Block
|
||||
from bigchaindb.web import server
|
||||
|
||||
|
||||
TPLS = {}
|
||||
|
||||
|
||||
TPLS['index-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
%(index)s
|
||||
"""
|
||||
|
||||
TPLS['api-index-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
%(api_index)s
|
||||
"""
|
||||
|
||||
TPLS['get-tx-id-request'] = """\
|
||||
GET /api/v1/transactions/%(txid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-id-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
%(tx)s
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-by-asset-request'] = """\
|
||||
GET /api/v1/transactions?operation=TRANSFER&asset_id=%(txid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-by-asset-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[%(tx_transfer)s,
|
||||
%(tx_transfer_last)s]
|
||||
"""
|
||||
|
||||
TPLS['post-tx-request'] = """\
|
||||
POST /transactions/ HTTP/1.1
|
||||
POST /api/v1/transactions/ HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
|
||||
|
@ -19,62 +67,215 @@ Content-Type: application/json
|
|||
|
||||
|
||||
TPLS['post-tx-response'] = """\
|
||||
HTTP/1.1 201 Created
|
||||
HTTP/1.1 202 Accepted
|
||||
Content-Type: application/json
|
||||
|
||||
%(tx)s
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-status-request'] = """\
|
||||
GET /transactions/%(txid)s/status HTTP/1.1
|
||||
TPLS['get-statuses-tx-request'] = """\
|
||||
GET /statuses?tx_id=%(txid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-status-response'] = """\
|
||||
TPLS['get-statuses-tx-invalid-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "valid"
|
||||
"status": "invalid"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-request'] = """\
|
||||
GET /transactions/%(txid)s HTTP/1.1
|
||||
TPLS['get-statuses-tx-valid-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "valid",
|
||||
"_links": {
|
||||
"tx": "/transactions/%(txid)s"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-statuses-block-request'] = """\
|
||||
GET /api/v1/statuses?block_id=%(blockid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-tx-response'] = """\
|
||||
TPLS['get-statuses-block-invalid-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
%(tx)s
|
||||
{
|
||||
"status": "invalid"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-statuses-block-valid-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "valid",
|
||||
"_links": {
|
||||
"block": "/blocks/%(blockid)s"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-block-request'] = """\
|
||||
GET /api/v1/blocks/%(blockid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-block-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
%(block)s
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-block-txid-request'] = """\
|
||||
GET /api/v1/blocks?tx_id=%(txid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-block-txid-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
%(block_list)s
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-vote-request'] = """\
|
||||
GET /api/v1/votes?block_id=%(blockid)s HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TPLS['get-vote-response'] = """\
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[%(vote)s]
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
""" Main function """
|
||||
|
||||
ctx = {}
|
||||
|
||||
def pretty_json(data):
|
||||
return json.dumps(data, indent=2, sort_keys=True)
|
||||
|
||||
client = server.create_app().test_client()
|
||||
|
||||
host = 'example.com:9984'
|
||||
|
||||
# HTTP Index
|
||||
res = client.get('/', environ_overrides={'HTTP_HOST': host})
|
||||
res_data = json.loads(res.data.decode())
|
||||
res_data['keyring'] = [
|
||||
"6qHyZew94NMmUTYyHnkZsB8cxJYuRNEiEpXHe1ih9QX3",
|
||||
"AdDuyrTyjrDt935YnFu4VBCVDhHtY2Y6rcy7x2TFeiRi"
|
||||
]
|
||||
res_data['public_key'] = 'NC8c8rYcAhyKVpx1PCV65CBmyq4YUbLysy3Rqrg8L8mz'
|
||||
ctx['index'] = pretty_json(res_data)
|
||||
|
||||
# API index
|
||||
res = client.get('/api/v1/', environ_overrides={'HTTP_HOST': host})
|
||||
ctx['api_index'] = pretty_json(json.loads(res.data.decode()))
|
||||
|
||||
# tx create
|
||||
privkey = 'CfdqtD7sS7FgkMoGPXw55MVGGFwQLAoHYTcBhZDtF99Z'
|
||||
pubkey = '4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD'
|
||||
tx = Transaction.create([pubkey], [([pubkey], 1)])
|
||||
asset = {'msg': 'Hello BigchainDB!'}
|
||||
tx = Transaction.create([pubkey], [([pubkey], 1)], asset=asset, metadata={'sequence': 0})
|
||||
tx = tx.sign([privkey])
|
||||
tx_json = json.dumps(tx.to_dict(), indent=2, sort_keys=True)
|
||||
ctx['tx'] = pretty_json(tx.to_dict())
|
||||
ctx['public_keys'] = tx.outputs[0].public_keys[0]
|
||||
ctx['txid'] = tx.id
|
||||
|
||||
# tx transfer
|
||||
privkey_transfer = '3AeWpPdhEZzWLYfkfYHBfMFC2r1f8HEaGS9NtbbKssya'
|
||||
pubkey_transfer = '3yfQPHeWAa1MxTX9Zf9176QqcpcnWcanVZZbaHb8B3h9'
|
||||
|
||||
cid = 0
|
||||
input_ = Input(fulfillment=tx.outputs[cid].fulfillment,
|
||||
fulfills=TransactionLink(txid=tx.id, output=cid),
|
||||
owners_before=tx.outputs[cid].public_keys)
|
||||
tx_transfer = Transaction.transfer([input_], [([pubkey_transfer], 1)], asset_id=tx.id, metadata={'sequence': 1})
|
||||
tx_transfer = tx_transfer.sign([privkey])
|
||||
ctx['tx_transfer'] = pretty_json(tx_transfer.to_dict())
|
||||
ctx['public_keys_transfer'] = tx_transfer.outputs[0].public_keys[0]
|
||||
ctx['tx_transfer_id'] = tx_transfer.id
|
||||
|
||||
# privkey_transfer_last = 'sG3jWDtdTXUidBJK53ucSTrosktG616U3tQHBk81eQe'
|
||||
pubkey_transfer_last = '3Af3fhhjU6d9WecEM9Uw5hfom9kNEwE7YuDWdqAUssqm'
|
||||
|
||||
cid = 0
|
||||
input_ = Input(fulfillment=tx_transfer.outputs[cid].fulfillment,
|
||||
fulfills=TransactionLink(txid=tx_transfer.id, output=cid),
|
||||
owners_before=tx_transfer.outputs[cid].public_keys)
|
||||
tx_transfer_last = Transaction.transfer([input_], [([pubkey_transfer_last], 1)],
|
||||
asset_id=tx.id, metadata={'sequence': 2})
|
||||
tx_transfer_last = tx_transfer_last.sign([privkey_transfer])
|
||||
ctx['tx_transfer_last'] = pretty_json(tx_transfer_last.to_dict())
|
||||
ctx['tx_transfer_last_id'] = tx_transfer_last.id
|
||||
ctx['public_keys_transfer_last'] = tx_transfer_last.outputs[0].public_keys[0]
|
||||
|
||||
# block
|
||||
node_private = "5G2kE1zJAgTajkVSbPAQWo4c2izvtwqaNHYsaNpbbvxX"
|
||||
node_public = "DngBurxfeNVKZWCEcDnLj1eMPAS7focUZTE5FndFGuHT"
|
||||
signature = "53wxrEQDYk1dXzmvNSytbCfmNVnPqPkDQaTnAe8Jf43s6ssejPxezkCvUnGTnduNUmaLjhaan1iRLi3peu6s5DzA"
|
||||
block = Block(transactions=[tx], node_pubkey=node_public, voters=[node_public], signature=signature)
|
||||
ctx['block'] = pretty_json(block.to_dict())
|
||||
ctx['blockid'] = block.id
|
||||
|
||||
block_transfer = Block(transactions=[tx_transfer], node_pubkey=node_public,
|
||||
voters=[node_public], signature=signature)
|
||||
ctx['block_transfer'] = pretty_json(block.to_dict())
|
||||
|
||||
# vote
|
||||
DUMMY_SHA3 = '0123456789abcdef' * 4
|
||||
b = Bigchain(public_key=node_public, private_key=node_private)
|
||||
vote = b.vote(block.id, DUMMY_SHA3, True)
|
||||
ctx['vote'] = pretty_json(vote)
|
||||
|
||||
# block status
|
||||
block_list = [
|
||||
block_transfer.id,
|
||||
block.id
|
||||
]
|
||||
ctx['block_list'] = pretty_json(block_list)
|
||||
|
||||
base_path = os.path.join(os.path.dirname(__file__),
|
||||
'source/drivers-clients/samples')
|
||||
|
||||
if not os.path.exists(base_path):
|
||||
os.makedirs(base_path)
|
||||
|
||||
for name, tpl in TPLS.items():
|
||||
path = os.path.join(base_path, name + '.http')
|
||||
code = tpl % {'tx': tx_json, 'txid': tx.id}
|
||||
code = tpl % ctx
|
||||
with open(path, 'w') as handle:
|
||||
handle.write(code)
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ def render_section(section_name, obj):
|
|||
'type': property_type(prop),
|
||||
}]
|
||||
except Exception as exc:
|
||||
raise ValueError("Error rendering property: %s" % name, exc)
|
||||
raise ValueError('Error rendering property: %s' % name, exc)
|
||||
return '\n\n'.join(out + [''])
|
||||
|
||||
|
||||
|
@ -201,7 +201,7 @@ def property_description(prop):
|
|||
return property_description(resolve_ref(prop['$ref']))
|
||||
if 'anyOf' in prop:
|
||||
return property_description(prop['anyOf'][0])
|
||||
raise KeyError("description")
|
||||
raise KeyError('description')
|
||||
|
||||
|
||||
def property_type(prop):
|
||||
|
@ -214,7 +214,7 @@ def property_type(prop):
|
|||
return ' or '.join(property_type(p) for p in prop['anyOf'])
|
||||
if '$ref' in prop:
|
||||
return property_type(resolve_ref(prop['$ref']))
|
||||
raise ValueError("Could not resolve property type")
|
||||
raise ValueError('Could not resolve property type')
|
||||
|
||||
|
||||
DEFINITION_BASE_PATH = '#/definitions/'
|
||||
|
|
|
@ -18,6 +18,13 @@ The instructions that follow have been tested on Ubuntu 14.04, but may also work
|
|||
|
||||
**Note: Our Python scripts for deploying to AWS use Python 2 because Fabric doesn't work with Python 3.**
|
||||
|
||||
You must install the Python package named `fabric`, but it depends on the `cryptography` package, and that depends on some OS-level packages. On Ubuntu 14.04, you can install those OS-level packages using:
|
||||
```text
|
||||
sudo apt-get install build-essential libssl-dev libffi-dev python-dev
|
||||
```
|
||||
|
||||
For other operating systems, see [the installation instructions for the `cryptography` package](https://cryptography.io/en/latest/installation/).
|
||||
|
||||
Maybe create a Python 2 virtual environment and activate it. Then install the following Python packages (in that virtual environment):
|
||||
```text
|
||||
pip install fabric fabtools requests boto3 awscli
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# The Digital Asset Model
|
||||
|
||||
The asset ID is the same as the ID of the CREATE transaction that defined the asset.
|
||||
To avoid redundant data in transactions, the digital asset model is different for `CREATE` and `TRANSFER` transactions.
|
||||
|
||||
In the case of a CREATE transaction, the transaction ID is duplicated into the asset object for clarity and consistency in the database. The CREATE transaction also contains a user definable payload to describe the asset:
|
||||
A digital asset's properties are defined in a `CREATE` transaction with the following model:
|
||||
```json
|
||||
{
|
||||
"id": "<same as transaction ID (sha3-256 hash)>",
|
||||
"data": "<json document>"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -7,25 +7,27 @@ The BigchainDB core dev team develops BigchainDB on recent Ubuntu and Fedora dis
|
|||
|
||||
## Option A: Using a Local Dev Machine
|
||||
|
||||
First, read through the BigchainDB [CONTRIBUTING.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CONTRIBUTING.md). It outlines the steps to setup a machine for developing and testing BigchainDB.
|
||||
Read through the BigchainDB [CONTRIBUTING.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CONTRIBUTING.md). It outlines the steps to setup a machine for developing and testing BigchainDB.
|
||||
|
||||
Next, create a default BigchainDB config file (in `$HOME/.bigchaindb`):
|
||||
### With RethinkDB
|
||||
|
||||
Create a default BigchainDB config file (in `$HOME/.bigchaindb`):
|
||||
```text
|
||||
bigchaindb -y configure
|
||||
$ bigchaindb -y configure rethinkdb
|
||||
```
|
||||
|
||||
Note: [The BigchainDB CLI](../server-reference/bigchaindb-cli.html) and the [BigchainDB Configuration Settings](../server-reference/configuration.html) are documented elsewhere. (Click the links.)
|
||||
|
||||
Start RethinkDB using:
|
||||
```text
|
||||
rethinkdb
|
||||
$ rethinkdb
|
||||
```
|
||||
|
||||
You can verify that RethinkDB is running by opening the RethinkDB web interface in your web browser. It should be at [http://localhost:8080/](http://localhost:8080/).
|
||||
|
||||
To run BigchainDB Server, do:
|
||||
```text
|
||||
bigchaindb start
|
||||
$ bigchaindb start
|
||||
```
|
||||
|
||||
You can [run all the unit tests](running-unit-tests.html) to test your installation.
|
||||
|
@ -33,13 +35,37 @@ You can [run all the unit tests](running-unit-tests.html) to test your installat
|
|||
The BigchainDB [CONTRIBUTING.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CONTRIBUTING.md) has more details about how to contribute.
|
||||
|
||||
|
||||
## Option B: Using a Dev Machine on Cloud9
|
||||
### With MongoDB
|
||||
|
||||
Ian Worrall of [Encrypted Labs](http://www.encryptedlabs.com/) wrote a document (PDF) explaining how to set up a BigchainDB (Server) dev machine on Cloud9:
|
||||
Create a default BigchainDB config file (in `$HOME/.bigchaindb`):
|
||||
```text
|
||||
$ bigchaindb -y configure mongodb
|
||||
```
|
||||
|
||||
[Download that document from GitHub](https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/docs/server/source/_static/cloud9.pdf)
|
||||
Note: [The BigchainDB CLI](../server-reference/bigchaindb-cli.html) and the [BigchainDB Configuration Settings](../server-reference/configuration.html) are documented elsewhere. (Click the links.)
|
||||
|
||||
## Option C: Using a Local Dev Machine and Docker
|
||||
Start MongoDB __3.4+__ using:
|
||||
```text
|
||||
$ mongod --replSet=bigchain-rs
|
||||
```
|
||||
|
||||
You can verify that MongoDB is running correctly by checking the output of the
|
||||
previous command for the line:
|
||||
```text
|
||||
waiting for connections on port 27017
|
||||
```
|
||||
|
||||
To run BigchainDB Server, do:
|
||||
```text
|
||||
$ bigchaindb start
|
||||
```
|
||||
|
||||
You can [run all the unit tests](running-unit-tests.html) to test your installation.
|
||||
|
||||
The BigchainDB [CONTRIBUTING.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CONTRIBUTING.md) has more details about how to contribute.
|
||||
|
||||
|
||||
## Option B: Using a Local Dev Machine and Docker
|
||||
|
||||
You need to have recent versions of [Docker Engine](https://docs.docker.com/engine/installation/)
|
||||
and (Docker) [Compose](https://docs.docker.com/compose/install/).
|
||||
|
@ -50,6 +76,8 @@ Build the images:
|
|||
docker-compose build
|
||||
```
|
||||
|
||||
### Docker with RethinkDB
|
||||
|
||||
**Note**: If you're upgrading BigchainDB and have previously already built the images, you may need
|
||||
to rebuild them after the upgrade to install any new dependencies.
|
||||
|
||||
|
@ -62,7 +90,7 @@ docker-compose up -d rdb
|
|||
The RethinkDB web interface should be accessible at <http://localhost:58080/>.
|
||||
Depending on which platform, and/or how you are running docker, you may need
|
||||
to change `localhost` for the `ip` of the machine that is running docker. As a
|
||||
dummy example, if the `ip` of that machine was `0.0.0.0`, you would accees the
|
||||
dummy example, if the `ip` of that machine was `0.0.0.0`, you would access the
|
||||
web interface at: <http://0.0.0.0:58080/>.
|
||||
|
||||
Start a BigchainDB node:
|
||||
|
@ -83,6 +111,40 @@ If you wish to run the tests:
|
|||
docker-compose run --rm bdb py.test -v -n auto
|
||||
```
|
||||
|
||||
### Docker with MongoDB
|
||||
|
||||
Start MongoDB:
|
||||
|
||||
```bash
|
||||
docker-compose up -d mdb
|
||||
```
|
||||
|
||||
MongoDB should now be up and running. You can check the port binding for the
|
||||
MongoDB driver port using:
|
||||
```bash
|
||||
$ docker-compose port mdb 27017
|
||||
```
|
||||
|
||||
Start a BigchainDB node:
|
||||
|
||||
```bash
|
||||
docker-compose up -d bdb-mdb
|
||||
```
|
||||
|
||||
You can monitor the logs:
|
||||
|
||||
```bash
|
||||
docker-compose logs -f bdb-mdb
|
||||
```
|
||||
|
||||
If you wish to run the tests:
|
||||
|
||||
```bash
|
||||
docker-compose run --rm bdb-mdb py.test -v --database-backend=mongodb
|
||||
```
|
||||
|
||||
### Accessing the HTTP API
|
||||
|
||||
A quick check to make sure that the BigchainDB server API is operational:
|
||||
|
||||
```bash
|
||||
|
@ -123,3 +185,9 @@ root:
|
|||
```bash
|
||||
curl 0.0.0.0:32772
|
||||
```
|
||||
|
||||
## Option C: Using a Dev Machine on Cloud9
|
||||
|
||||
Ian Worrall of [Encrypted Labs](http://www.encryptedlabs.com/) wrote a document (PDF) explaining how to set up a BigchainDB (Server) dev machine on Cloud9:
|
||||
|
||||
[Download that document from GitHub](https://raw.githubusercontent.com/bigchaindb/bigchaindb/master/docs/server/source/_static/cloud9.pdf)
|
||||
|
|
|
@ -1,58 +1,136 @@
|
|||
The HTTP Client-Server API
|
||||
==========================
|
||||
|
||||
.. note::
|
||||
|
||||
The HTTP client-server API is currently quite rudimentary. For example,
|
||||
there is no ability to do complex queries using the HTTP API. We plan to add
|
||||
more querying capabilities in the future.
|
||||
|
||||
This page assumes you already know an API Root URL
|
||||
for a BigchainDB node or reverse proxy.
|
||||
It should be something like ``http://apihosting4u.net:9984``
|
||||
or ``http://12.34.56.78:9984``.
|
||||
It should be something like ``https://example.com:9984``
|
||||
or ``https://12.34.56.78:9984``.
|
||||
|
||||
If you set up a BigchainDB node or reverse proxy yourself,
|
||||
and you're not sure what the API Root URL is,
|
||||
then see the last section of this page for help.
|
||||
|
||||
|
||||
API Root URL
|
||||
------------
|
||||
|
||||
If you send an HTTP GET request to the API Root URL
|
||||
e.g. ``http://localhost:9984``
|
||||
or ``http://apihosting4u.net:9984``
|
||||
(with no ``/api/v1/`` on the end),
|
||||
then you should get an HTTP response
|
||||
with something like the following in the body:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"keyring": [
|
||||
"6qHyZew94NMmUTYyHnkZsB8cxJYuRNEiEpXHe1ih9QX3",
|
||||
"AdDuyrTyjrDt935YnFu4VBCVDhHtY2Y6rcy7x2TFeiRi"
|
||||
],
|
||||
"public_key": "AiygKSRhZWTxxYT4AfgKoTG4TZAoPsWoEt6C6bLq4jJR",
|
||||
"software": "BigchainDB",
|
||||
"version": "0.6.0"
|
||||
}
|
||||
|
||||
|
||||
POST /transactions/
|
||||
BigchainDB Root URL
|
||||
-------------------
|
||||
|
||||
.. http:post:: /transactions/
|
||||
If you send an HTTP GET request to the BigchainDB Root URL
|
||||
e.g. ``http://localhost:9984``
|
||||
or ``https://example.com:9984``
|
||||
(with no ``/api/v1/`` on the end),
|
||||
then you should get an HTTP response
|
||||
with something like the following in the body:
|
||||
|
||||
.. literalinclude:: samples/index-response.http
|
||||
:language: http
|
||||
|
||||
|
||||
API Root Endpoint
|
||||
-------------------
|
||||
|
||||
If you send an HTTP GET request to the API Root Endpoint
|
||||
e.g. ``http://localhost:9984/api/v1/``
|
||||
or ``https://example.com:9984/api/v1/``,
|
||||
then you should get an HTTP response
|
||||
that allows you to discover the BigchainDB API endpoints:
|
||||
|
||||
.. literalinclude:: samples/api-index-response.http
|
||||
:language: http
|
||||
|
||||
|
||||
Transactions
|
||||
-------------------
|
||||
|
||||
.. http:get:: /api/v1/transactions/{tx_id}
|
||||
|
||||
Get the transaction with the ID ``tx_id``.
|
||||
|
||||
This endpoint returns a transaction only if a ``VALID`` block on
|
||||
``bigchain`` exists.
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-id-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-id-response.http
|
||||
:language: http
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
|
||||
:statuscode 200: A transaction with that ID was found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
|
||||
.. http:get:: /api/v1/transactions
|
||||
|
||||
The unfiltered ``/api/v1/transactions`` endpoint without any query parameters
|
||||
returns a status code `400`. For valid filters, see the sections below.
|
||||
|
||||
There are however filtered requests that might come of use, given the endpoint is
|
||||
queried correctly. Some of them include retrieving a list of transactions
|
||||
that include:
|
||||
|
||||
* `Transactions related to a specific asset <#get--transactions?asset_id=asset_id&operation=CREATE|TRANSFER>`_
|
||||
|
||||
In this section, we've listed those particular requests, as they will likely
|
||||
to be very handy when implementing your application on top of BigchainDB.
|
||||
|
||||
.. note::
|
||||
Looking up transactions with a specific ``metadata`` field is currently not supported,
|
||||
however, providing a way to query based on ``metadata`` data is on our roadmap.
|
||||
|
||||
A generalization of those parameters follows:
|
||||
|
||||
:query string asset_id: The ID of the asset.
|
||||
|
||||
:query string operation: (Optional) One of the two supported operations of a transaction: ``CREATE``, ``TRANSFER``.
|
||||
|
||||
.. http:get:: /api/v1/transactions?asset_id={asset_id}&operation={CREATE|TRANSFER}
|
||||
|
||||
Get a list of transactions that use an asset with the ID ``asset_id``.
|
||||
Every ``TRANSFER`` transaction that originates from a ``CREATE`` transaction
|
||||
with ``asset_id`` will be included. This allows users to query the entire history or
|
||||
provenance of an asset.
|
||||
|
||||
This endpoint returns transactions only if they are decided ``VALID`` by the server.
|
||||
|
||||
:query string operation: (Optional) One of the two supported operations of a transaction: ``CREATE``, ``TRANSFER``.
|
||||
|
||||
:query string asset_id: asset ID.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-by-asset-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-by-asset-response.http
|
||||
:language: http
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
|
||||
:statuscode 200: A list of transactions containing an asset with ID ``asset_id`` was found and returned.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``asset_id`` querystring was not included in the request.
|
||||
|
||||
|
||||
.. http:post:: /api/v1/transactions
|
||||
|
||||
Push a new transaction.
|
||||
|
||||
Note: The posted transaction should be a valid and signed :doc:`transaction <../data-models/transaction-model>`.
|
||||
The steps to build a valid transaction are beyond the scope of this page.
|
||||
One would normally use a driver such as the `BigchainDB Python Driver
|
||||
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to
|
||||
build a valid transaction. The exact contents of a valid transaction depend
|
||||
on the associated public/private keypairs.
|
||||
.. note::
|
||||
The posted `transaction
|
||||
<https://docs.bigchaindb.com/projects/server/en/latest/data-models/transaction-model.html>`_
|
||||
should be structurally valid and not spending an already spent output.
|
||||
The steps to build a valid transaction are beyond the scope of this page.
|
||||
One would normally use a driver such as the `BigchainDB Python Driver
|
||||
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_
|
||||
to build a valid transaction.
|
||||
|
||||
**Example request**:
|
||||
|
||||
|
@ -64,110 +142,255 @@ POST /transactions/
|
|||
.. literalinclude:: samples/post-tx-response.http
|
||||
:language: http
|
||||
|
||||
:statuscode 201: A new transaction was created.
|
||||
:statuscode 400: The transaction was invalid and not created.
|
||||
:resheader Content-Type: ``application/json``
|
||||
|
||||
:statuscode 202: The pushed transaction was accepted in the ``BACKLOG``, but the processing has not been completed.
|
||||
:statuscode 400: The transaction was malformed and not accepted in the ``BACKLOG``.
|
||||
|
||||
|
||||
GET /transactions/{tx_id}/status
|
||||
--------------------------------
|
||||
Transaction Outputs
|
||||
-------------------
|
||||
|
||||
.. http:get:: /transactions/{tx_id}/status
|
||||
|
||||
Get the status of the transaction with the ID ``tx_id``, if a transaction
|
||||
with that ``tx_id`` exists.
|
||||
|
||||
The possible status values are ``backlog``, ``undecided``, ``valid`` or
|
||||
``invalid``.
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-status-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-status-response.http
|
||||
:language: http
|
||||
|
||||
:statuscode 200: A transaction with that ID was found and the status is returned.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
The ``/api/v1/outputs`` endpoint returns transactions outputs filtered by a
|
||||
given public key, and optionally filtered to only include outputs that have
|
||||
not already been spent.
|
||||
|
||||
|
||||
GET /transactions/{tx_id}
|
||||
-------------------------
|
||||
.. http:get:: /api/v1/outputs?public_key={public_key}
|
||||
|
||||
.. http:get:: /transactions/{tx_id}
|
||||
Get transaction outputs by public key. The `public_key` parameter must be
|
||||
a base58 encoded ed25519 public key associated with transaction output
|
||||
ownership.
|
||||
|
||||
Get the transaction with the ID ``tx_id``.
|
||||
Returns a list of links to transaction outputs.
|
||||
|
||||
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED``
|
||||
block on ``bigchain``, if exists.
|
||||
:param public_key: Base58 encoded public key associated with output ownership. This parameter is mandatory and without it the endpoint will return a ``400`` response code.
|
||||
:param unspent: Boolean value ("true" or "false") indicating if the result set should be limited to outputs that are available to spend. Defaults to "false".
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-tx-response.http
|
||||
:language: http
|
||||
|
||||
:statuscode 200: A transaction with that ID was found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
|
||||
|
||||
GET /unspents/
|
||||
-------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This endpoint (unspents) is not yet implemented. We published it here for preview and comment.
|
||||
|
||||
|
||||
.. http:get:: /unspents?owner_after={owner_after}
|
||||
|
||||
Get a list of links to transactions' outputs that have not been used in
|
||||
a previous transaction and could hence be called unspent outputs
|
||||
(or simply: unspents).
|
||||
|
||||
This endpoint will return a ``HTTP 400 Bad Request`` if the querystring
|
||||
``owner_after`` happens to not be defined in the request.
|
||||
|
||||
Note that if unspents for a certain ``public_key`` have not been found by
|
||||
the server, this will result in the server returning a 200 OK HTTP status
|
||||
code and an empty list in the response's body.
|
||||
|
||||
:param owner_after: A public key, able to validly spend an output of a transaction, assuming the user also has the corresponding private key.
|
||||
:type owner_after: base58 encoded string
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /unspents?owner_after=1AAAbbb...ccc HTTP/1.1
|
||||
GET /api/v1/outputs?public_key=1AAAbbb...ccc HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/0",
|
||||
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/1"
|
||||
]
|
||||
|
||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``public_key`` querystring was not included in the request.
|
||||
|
||||
|
||||
Statuses
|
||||
--------------------------------
|
||||
|
||||
.. http:get:: /api/v1/statuses
|
||||
|
||||
Get the status of an asynchronously written transaction or block by their id.
|
||||
|
||||
A link to the resource is also provided in the returned payload under
|
||||
``_links``.
|
||||
|
||||
:query string tx_id: transaction ID
|
||||
:query string block_id: block ID
|
||||
|
||||
.. note::
|
||||
|
||||
Exactly one of the ``tx_id`` or ``block_id`` query parameters must be
|
||||
used together with this endpoint (see below for getting `transaction
|
||||
statuses <#get--statuses?tx_id=tx_id>`_ and `block statuses
|
||||
<#get--statuses?block_id=block_id>`_).
|
||||
|
||||
|
||||
.. http:get:: /api/v1/statuses?tx_id={tx_id}
|
||||
|
||||
Get the status of a transaction.
|
||||
|
||||
The possible status values are ``undecided``, ``valid`` or ``backlog``.
|
||||
If a transaction in neither of those states is found, a ``404 Not Found``
|
||||
HTTP status code is returned. `We're currently looking into ways to unambigously let the user know about a transaction's status that was included in an invalid block. <https://github.com/bigchaindb/bigchaindb/issues/1039>`_
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-statuses-tx-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-statuses-tx-valid-response.http
|
||||
:language: http
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
:resheader Location: Once the transaction has been persisted, this header will link to the actual resource.
|
||||
|
||||
:statuscode 200: A transaction with that ID was found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
|
||||
|
||||
.. http:get:: /api/v1/statuses?block_id={block_id}
|
||||
|
||||
Get the status of a block.
|
||||
|
||||
The possible status values are ``undecided``, ``valid`` or ``invalid``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-statuses-block-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-statuses-block-invalid-response.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-statuses-block-valid-response.http
|
||||
:language: http
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
:resheader Location: Once the block has been persisted, this header will link to the actual resource.
|
||||
|
||||
:statuscode 200: A block with that ID was found.
|
||||
:statuscode 404: A block with that ID was not found.
|
||||
|
||||
|
||||
Advanced Usage
|
||||
--------------------------------
|
||||
|
||||
The following endpoints are more advanced and meant for debugging and transparency purposes.
|
||||
|
||||
More precisely, the `blocks endpoint <#blocks>`_ allows you to retrieve a block by ``block_id`` as well the list of blocks that
|
||||
a certain transaction with ``tx_id`` occured in (a transaction can occur in multiple ``invalid`` blocks until it
|
||||
either gets rejected or validated by the system). This endpoint gives the ability to drill down on the lifecycle of a
|
||||
transaction
|
||||
|
||||
The `votes endpoint <#votes>`_ contains all the voting information for a specific block. So after retrieving the
|
||||
``block_id`` for a given ``tx_id``, one can now simply inspect the votes that happened at a specific time on that block.
|
||||
|
||||
|
||||
Blocks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. http:get:: /api/v1/blocks/{block_id}
|
||||
|
||||
Get the block with the ID ``block_id``. Any blocks, be they ``VALID``, ``UNDECIDED`` or ``INVALID`` will be
|
||||
returned. To check a block's status independently, use the `Statuses endpoint <#status>`_.
|
||||
To check the votes on a block, have a look at the `votes endpoint <#votes>`_.
|
||||
|
||||
:param block_id: block ID
|
||||
:type block_id: hex string
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-block-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-block-response.http
|
||||
:language: http
|
||||
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
|
||||
:statuscode 200: A block with that ID was found.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. just requesting ``/blocks`` without the ``block_id``.
|
||||
:statuscode 404: A block with that ID was not found.
|
||||
|
||||
|
||||
.. http:get:: /api/v1/blocks
|
||||
|
||||
The unfiltered ``/blocks`` endpoint without any query parameters returns a `400` status code.
|
||||
The list endpoint should be filtered with a ``tx_id`` query parameter,
|
||||
see the ``/blocks?tx_id={tx_id}&status={UNDECIDED|VALID|INVALID}``
|
||||
`endpoint <#get--blocks?tx_id=tx_id&status=UNDECIDED|VALID|INVALID>`_.
|
||||
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/blocks HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
HTTP/1.1 400 Bad Request
|
||||
|
||||
[
|
||||
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/0",
|
||||
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/1"
|
||||
]
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. just requesting ``/blocks`` without the ``block_id``.
|
||||
|
||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``owner_after`` querystring was not included in the request.
|
||||
.. http:get:: /api/v1/blocks?tx_id={tx_id}&status={UNDECIDED|VALID|INVALID}
|
||||
|
||||
Retrieve a list of ``block_id`` with their corresponding status that contain a transaction with the ID ``tx_id``.
|
||||
|
||||
Any blocks, be they ``UNDECIDED``, ``VALID`` or ``INVALID`` will be
|
||||
returned if no status filter is provided.
|
||||
|
||||
.. note::
|
||||
In case no block was found, an empty list and an HTTP status code
|
||||
``200 OK`` is returned, as the request was still successful.
|
||||
|
||||
:query string tx_id: transaction ID *(required)*
|
||||
:query string status: Filter blocks by their status. One of ``VALID``, ``UNDECIDED`` or ``INVALID``.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-block-txid-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-block-txid-response.http
|
||||
:language: http
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
|
||||
:statuscode 200: A list of blocks containing a transaction with ID ``tx_id`` was found and returned.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. just requesting ``/blocks``, without defining ``tx_id``.
|
||||
|
||||
|
||||
Votes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. http:get:: /api/v1/votes?block_id={block_id}
|
||||
|
||||
Retrieve a list of votes for a certain block with ID ``block_id``.
|
||||
To check for the validity of a vote, a user of this endpoint needs to
|
||||
perform the `following steps: <https://github.com/bigchaindb/bigchaindb/blob/8ebd93ed3273e983f5770b1617292aadf9f1462b/bigchaindb/util.py#L119>`_
|
||||
|
||||
1. Check if the vote's ``node_pubkey`` is allowed to vote.
|
||||
2. Verify the vote's signature against the vote's body (``vote.vote``) and ``node_pubkey``.
|
||||
|
||||
|
||||
:query string block_id: The block ID to filter the votes.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. literalinclude:: samples/get-vote-request.http
|
||||
:language: http
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. literalinclude:: samples/get-vote-response.http
|
||||
:language: http
|
||||
|
||||
:resheader Content-Type: ``application/json``
|
||||
|
||||
:statuscode 200: A list of votes voting for a block with ID ``block_id`` was found and returned.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. just requesting ``/votes``, without defining ``block_id``.
|
||||
|
||||
|
||||
Determining the API Root URL
|
||||
|
@ -190,18 +413,18 @@ the HTTP API publicly accessible.
|
|||
If the API endpoint is publicly accessible,
|
||||
then the public API Root URL is determined as follows:
|
||||
|
||||
- The public IP address (like 12.34.56.78)
|
||||
is the public IP address of the machine exposing
|
||||
the HTTP API to the public internet (e.g. either the machine hosting
|
||||
Gunicorn or the machine running the reverse proxy such as Nginx).
|
||||
- The public IP address (like 12.34.56.78)
|
||||
is the public IP address of the machine exposing
|
||||
the HTTP API to the public internet (e.g. either the machine hosting
|
||||
Gunicorn or the machine running the reverse proxy such as Nginx).
|
||||
It's determined by AWS, Azure, Rackspace, or whoever is hosting the machine.
|
||||
|
||||
- The DNS hostname (like apihosting4u.net) is determined by DNS records,
|
||||
such as an "A Record" associating apihosting4u.net with 12.34.56.78
|
||||
- The DNS hostname (like example.com) is determined by DNS records,
|
||||
such as an "A Record" associating example.com with 12.34.56.78
|
||||
|
||||
- The port (like 9984) is determined by the ``server.bind`` setting
|
||||
if Gunicorn is exposed directly to the public Internet.
|
||||
If a reverse proxy (like Nginx) is exposed directly to the public Internet
|
||||
instead, then it could expose the HTTP API on whatever port it wants to.
|
||||
(It should expose the HTTP API on port 9984, but it's not bound to do
|
||||
- The port (like 9984) is determined by the ``server.bind`` setting
|
||||
if Gunicorn is exposed directly to the public Internet.
|
||||
If a reverse proxy (like Nginx) is exposed directly to the public Internet
|
||||
instead, then it could expose the HTTP API on whatever port it wants to.
|
||||
(It should expose the HTTP API on port 9984, but it's not bound to do
|
||||
that by anything other than convention.)
|
||||
|
|
|
@ -1,35 +1,56 @@
|
|||
# Quickstart
|
||||
|
||||
This page has instructions to set up a single stand-alone BigchainDB node for learning or experimenting. Instructions for other cases are [elsewhere](introduction.html). We will assume you're using Ubuntu 14.04 or similar. If you're not using Linux, then you might try [running BigchainDB with Docker](appendices/run-with-docker.html).
|
||||
This page has instructions to set up a single stand-alone BigchainDB node for learning or experimenting. Instructions for other cases are [elsewhere](introduction.html). We will assume you're using Ubuntu 16.04 or similar. If you're not using Linux, then you might try [running BigchainDB with Docker](appendices/run-with-docker.html).
|
||||
|
||||
A. [Install RethinkDB Server](https://rethinkdb.com/docs/install/ubuntu/)
|
||||
A. Install the database backend.
|
||||
|
||||
B. Open a Terminal and run RethinkDB Server with the command:
|
||||
[Install RethinkDB Server](https://rethinkdb.com/docs/install/ubuntu/) or
|
||||
[Install MongoDB Server 3.4+](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/)
|
||||
|
||||
B. Run the database backend. Open a Terminal and run the command:
|
||||
|
||||
with RethinkDB
|
||||
```text
|
||||
rethinkdb
|
||||
$ rethinkdb
|
||||
```
|
||||
|
||||
C. Ubuntu 14.04 already has Python 3.4, so you don't need to install it, but you do need to install a couple other things:
|
||||
with MongoDB __3.4+__
|
||||
```text
|
||||
sudo apt-get update
|
||||
sudo apt-get install g++ python3-dev libffi-dev
|
||||
$ mongod --replSet=bigchain-rs
|
||||
```
|
||||
|
||||
C. Ubuntu 16.04 already has Python 3.5, so you don't need to install it, but you do need to install some other things:
|
||||
```text
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install g++ python3-dev libffi-dev
|
||||
```
|
||||
|
||||
D. Get the latest version of pip and setuptools:
|
||||
```text
|
||||
sudo apt-get install python3-pip
|
||||
sudo pip3 install --upgrade pip setuptools
|
||||
$ sudo apt-get install python3-pip
|
||||
$ sudo pip3 install --upgrade pip setuptools
|
||||
```
|
||||
|
||||
E. Install the `bigchaindb` Python package from PyPI:
|
||||
```text
|
||||
sudo pip3 install bigchaindb
|
||||
$ sudo pip3 install bigchaindb
|
||||
```
|
||||
|
||||
F. Configure and run BigchainDB Server:
|
||||
F. Configure the BigchainDB Server: and run BigchainDB Server:
|
||||
|
||||
with RethinkDB
|
||||
```text
|
||||
bigchaindb -y configure
|
||||
bigchaindb start
|
||||
$ bigchaindb -y configure rethinkdb
|
||||
```
|
||||
|
||||
with MongoDB
|
||||
```text
|
||||
$ bigchaindb -y configure mongodb
|
||||
```
|
||||
|
||||
G. Run the BigchainDB Server:
|
||||
```text
|
||||
$ bigchaindb start
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
|
|
@ -15,18 +15,22 @@ Show the version number. `bigchaindb -v` does the same thing.
|
|||
|
||||
## bigchaindb configure
|
||||
|
||||
Generate a local config file (which can be used to set some or all [BigchainDB node configuration settings](configuration.html)). It will auto-generate a public-private keypair and then ask you for the values of other configuration settings. If you press Enter for a value, it will use the default value.
|
||||
Generate a local configuration file (which can be used to set some or all [BigchainDB node configuration settings](configuration.html)). It will auto-generate a public-private keypair and then ask you for the values of other configuration settings. If you press Enter for a value, it will use the default value.
|
||||
|
||||
Since BigchainDB supports multiple databases you need to always specify the
|
||||
database backend that you want to use. At this point only two database backends
|
||||
are supported: `rethinkdb` and `mongodb`.
|
||||
|
||||
If you use the `-c` command-line option, it will generate the file at the specified path:
|
||||
```text
|
||||
bigchaindb -c path/to/new_config.json configure
|
||||
bigchaindb -c path/to/new_config.json configure rethinkdb
|
||||
```
|
||||
|
||||
If you don't use the `-c` command-line option, the file will be written to `$HOME/.bigchaindb` (the default location where BigchainDB looks for a config file, if one isn't specified).
|
||||
|
||||
If you use the `-y` command-line option, then there won't be any interactive prompts: it will just generate a keypair and use the default values for all the other configuration settings.
|
||||
```text
|
||||
bigchaindb -y configure
|
||||
bigchaindb -y configure rethinkdb
|
||||
```
|
||||
|
||||
|
||||
|
@ -83,3 +87,25 @@ Set the number of replicas (of each shard) in the underlying datastore. For exam
|
|||
```text
|
||||
$ bigchaindb set-replicas 3
|
||||
```
|
||||
|
||||
## bigchaindb add-replicas
|
||||
|
||||
This command is specific to MongoDB so it will only run if BigchainDB is
|
||||
configured with `mongodb` as the backend.
|
||||
|
||||
This command is used to add nodes to a BigchainDB cluster. It accepts a list of
|
||||
space separated hosts in the form _hostname:port_:
|
||||
```text
|
||||
$ bigchaindb add-replicas server1.com:27017 server2.com:27017 server3.com:27017
|
||||
```
|
||||
|
||||
## bigchaindb remove-replicas
|
||||
|
||||
This command is specific to MongoDB so it will only run if BigchainDB is
|
||||
configured with `mongodb` as the backend.
|
||||
|
||||
This command is used to remove nodes from a BigchainDB cluster. It accepts a
|
||||
list of space separated hosts in the form _hostname:port_:
|
||||
```text
|
||||
$ bigchaindb remove-replicas server1.com:27017 server2.com:27017 server3.com:27017
|
||||
```
|
||||
|
|
1
setup.py
1
setup.py
|
@ -45,6 +45,7 @@ tests_require = [
|
|||
'coverage',
|
||||
'pep8',
|
||||
'flake8',
|
||||
'flake8-quotes==0.8.1',
|
||||
'pylint',
|
||||
'pytest>=3.0.0',
|
||||
'pytest-catchlog>=1.2.2',
|
||||
|
|
|
@ -90,95 +90,6 @@ def test_asset_id_mismatch(b, user_pk):
|
|||
Transaction.get_asset_id([tx1, tx2])
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_get_transactions_by_asset_id(b, user_pk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx_create = b.get_owned_ids(user_pk).pop()
|
||||
tx_create = b.get_transaction(tx_create.txid)
|
||||
asset_id = tx_create.id
|
||||
txs = b.get_transactions_by_asset_id(asset_id)
|
||||
|
||||
assert len(txs) == 1
|
||||
assert txs[0].id == tx_create.id
|
||||
assert txs[0].id == asset_id
|
||||
|
||||
# create a transfer transaction
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.id)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
# create the block
|
||||
block = b.create_block([tx_transfer_signed])
|
||||
b.write_block(block)
|
||||
# vote the block valid
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
txs = b.get_transactions_by_asset_id(asset_id)
|
||||
|
||||
assert len(txs) == 2
|
||||
assert {tx_create.id, tx_transfer.id} == set(tx.id for tx in txs)
|
||||
assert asset_id == Transaction.get_asset_id(txs)
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_get_transactions_by_asset_id_with_invalid_block(b, user_pk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx_create = b.get_owned_ids(user_pk).pop()
|
||||
tx_create = b.get_transaction(tx_create.txid)
|
||||
asset_id = tx_create.id
|
||||
txs = b.get_transactions_by_asset_id(asset_id)
|
||||
|
||||
assert len(txs) == 1
|
||||
assert txs[0].id == tx_create.id
|
||||
assert txs[0].id == asset_id
|
||||
|
||||
# create a transfer transaction
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.id)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
# create the block
|
||||
block = b.create_block([tx_transfer_signed])
|
||||
b.write_block(block)
|
||||
# vote the block invalid
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, False)
|
||||
b.write_vote(vote)
|
||||
|
||||
txs = b.get_transactions_by_asset_id(asset_id)
|
||||
|
||||
assert len(txs) == 1
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_get_asset_by_id(b, user_pk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx_create = b.get_owned_ids(user_pk).pop()
|
||||
tx_create = b.get_transaction(tx_create.txid)
|
||||
|
||||
# create a transfer transaction
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.id)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
# create the block
|
||||
block = b.create_block([tx_transfer_signed])
|
||||
b.write_block(block)
|
||||
# vote the block valid
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
asset_id = Transaction.get_asset_id([tx_create, tx_transfer])
|
||||
txs = b.get_transactions_by_asset_id(asset_id)
|
||||
assert len(txs) == 2
|
||||
|
||||
asset = b.get_asset_by_id(asset_id)
|
||||
assert asset == tx_create.asset
|
||||
|
||||
|
||||
def test_create_invalid_divisible_asset(b, user_pk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
|
|
@ -125,24 +125,6 @@ def test_get_block_status_from_transaction(create_tx):
|
|||
assert block_db['block']['voters'] == block.voters
|
||||
|
||||
|
||||
def test_get_txids_by_asset_id(signed_create_tx, signed_transfer_tx):
|
||||
from bigchaindb.backend import connect, query
|
||||
from bigchaindb.models import Block
|
||||
conn = connect()
|
||||
|
||||
# create and insert two blocks, one for the create and one for the
|
||||
# transfer transaction
|
||||
block = Block(transactions=[signed_create_tx])
|
||||
conn.db.bigchain.insert_one(block.to_dict())
|
||||
block = Block(transactions=[signed_transfer_tx])
|
||||
conn.db.bigchain.insert_one(block.to_dict())
|
||||
|
||||
txids = list(query.get_txids_by_asset_id(conn, signed_create_tx.id))
|
||||
|
||||
assert len(txids) == 2
|
||||
assert txids == [signed_create_tx.id, signed_transfer_tx.id]
|
||||
|
||||
|
||||
def test_get_asset_by_id(create_tx):
|
||||
from bigchaindb.backend import connect, query
|
||||
from bigchaindb.models import Block
|
||||
|
@ -366,3 +348,30 @@ def test_get_unvoted_blocks(signed_create_tx):
|
|||
|
||||
assert len(unvoted_blocks) == 1
|
||||
assert unvoted_blocks[0] == block.to_dict()
|
||||
|
||||
|
||||
def test_get_txids_filtered(signed_create_tx, signed_transfer_tx):
|
||||
from bigchaindb.backend import connect, query
|
||||
from bigchaindb.models import Block, Transaction
|
||||
conn = connect()
|
||||
|
||||
# create and insert two blocks, one for the create and one for the
|
||||
# transfer transaction
|
||||
block = Block(transactions=[signed_create_tx])
|
||||
conn.db.bigchain.insert_one(block.to_dict())
|
||||
block = Block(transactions=[signed_transfer_tx])
|
||||
conn.db.bigchain.insert_one(block.to_dict())
|
||||
|
||||
asset_id = Transaction.get_asset_id([signed_create_tx, signed_transfer_tx])
|
||||
|
||||
# Test get by just asset id
|
||||
txids = set(query.get_txids_filtered(conn, asset_id))
|
||||
assert txids == {signed_create_tx.id, signed_transfer_tx.id}
|
||||
|
||||
# Test get by asset and CREATE
|
||||
txids = set(query.get_txids_filtered(conn, asset_id, Transaction.CREATE))
|
||||
assert txids == {signed_create_tx.id}
|
||||
|
||||
# Test get by asset and TRANSFER
|
||||
txids = set(query.get_txids_filtered(conn, asset_id, Transaction.TRANSFER))
|
||||
assert txids == {signed_transfer_tx.id}
|
||||
|
|
|
@ -57,8 +57,8 @@ def test_set_shards_dry_run(rdb_conn, db_name, db_conn):
|
|||
@pytest.mark.bdb
|
||||
@pytest.mark.skipif(
|
||||
_count_rethinkdb_servers() < 2,
|
||||
reason=("Requires at least two servers. It's impossible to have"
|
||||
"more replicas of the data than there are servers.")
|
||||
reason=('Requires at least two servers. It\'s impossible to have'
|
||||
'more replicas of the data than there are servers.')
|
||||
)
|
||||
def test_set_replicas(rdb_conn, db_name, db_conn):
|
||||
from bigchaindb.backend.schema import TABLES
|
||||
|
@ -85,8 +85,8 @@ def test_set_replicas(rdb_conn, db_name, db_conn):
|
|||
@pytest.mark.bdb
|
||||
@pytest.mark.skipif(
|
||||
_count_rethinkdb_servers() < 2,
|
||||
reason=("Requires at least two servers. It's impossible to have"
|
||||
"more replicas of the data than there are servers.")
|
||||
reason=('Requires at least two servers. It\'s impossible to have'
|
||||
'more replicas of the data than there are servers.')
|
||||
)
|
||||
def test_set_replicas_dry_run(rdb_conn, db_name, db_conn):
|
||||
from bigchaindb.backend.schema import TABLES
|
||||
|
@ -109,8 +109,8 @@ def test_set_replicas_dry_run(rdb_conn, db_name, db_conn):
|
|||
@pytest.mark.bdb
|
||||
@pytest.mark.skipif(
|
||||
_count_rethinkdb_servers() < 2,
|
||||
reason=("Requires at least two servers. It's impossible to have"
|
||||
"more replicas of the data than there are servers.")
|
||||
reason=('Requires at least two servers. It\'s impossible to have'
|
||||
'more replicas of the data than there are servers.')
|
||||
)
|
||||
def test_reconfigure(rdb_conn, db_name, db_conn):
|
||||
from bigchaindb.backend.rethinkdb.admin import reconfigure
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import time
|
||||
import multiprocessing as mp
|
||||
from threading import Thread
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import rethinkdb as r
|
||||
|
@ -118,3 +119,15 @@ def test_changefeed_reconnects_when_connection_lost(monkeypatch):
|
|||
|
||||
fact = changefeed.outqueue.get()['fact']
|
||||
assert fact == 'Cats sleep 70% of their lives.'
|
||||
|
||||
|
||||
@patch('rethinkdb.connect')
|
||||
def test_connection_happens_one_time_if_successful(mock_connect):
|
||||
from bigchaindb.backend import connect
|
||||
|
||||
query = r.expr('1')
|
||||
conn = connect('rethinkdb', 'localhost', 1337, 'whatev')
|
||||
conn.run(query)
|
||||
mock_connect.assert_called_once_with(host='localhost',
|
||||
port=1337,
|
||||
db='whatev')
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from pytest import mark, raises
|
||||
|
||||
|
||||
|
@ -25,7 +23,7 @@ def test_schema(schema_func_name, args_qty):
|
|||
('get_stale_transactions', 1),
|
||||
('get_blocks_status_from_transaction', 1),
|
||||
('get_transaction_from_backlog', 1),
|
||||
('get_txids_by_asset_id', 1),
|
||||
('get_txids_filtered', 1),
|
||||
('get_asset_by_id', 1),
|
||||
('get_owned_ids', 1),
|
||||
('get_votes_by_block_id', 1),
|
||||
|
@ -68,33 +66,6 @@ def test_changefeed_class(changefeed_class_func_name, args_qty):
|
|||
changefeed_class_func(None, *range(args_qty))
|
||||
|
||||
|
||||
@patch('bigchaindb.backend.schema.create_indexes',
|
||||
autospec=True, return_value=None)
|
||||
@patch('bigchaindb.backend.schema.create_tables',
|
||||
autospec=True, return_value=None)
|
||||
@patch('bigchaindb.backend.schema.create_database',
|
||||
autospec=True, return_value=None)
|
||||
def test_init_database(mock_create_database, mock_create_tables,
|
||||
mock_create_indexes):
|
||||
from bigchaindb.backend.schema import init_database
|
||||
from bigchaindb.backend.rethinkdb.connection import RethinkDBConnection
|
||||
from bigchaindb.backend.mongodb.connection import MongoDBConnection
|
||||
|
||||
# rethinkdb
|
||||
conn = RethinkDBConnection('host', 'port', 'dbname')
|
||||
init_database(connection=conn, dbname='mickeymouse')
|
||||
mock_create_database.assert_called_with(conn, 'mickeymouse')
|
||||
mock_create_tables.assert_called_with(conn, 'mickeymouse')
|
||||
mock_create_indexes.assert_called_with(conn, 'mickeymouse')
|
||||
|
||||
# mongodb
|
||||
conn = MongoDBConnection('host', 'port', 'dbname', replicaset='rs')
|
||||
init_database(connection=conn, dbname='mickeymouse')
|
||||
mock_create_database.assert_called_with(conn, 'mickeymouse')
|
||||
mock_create_tables.assert_called_with(conn, 'mickeymouse')
|
||||
mock_create_indexes.assert_called_with(conn, 'mickeymouse')
|
||||
|
||||
|
||||
@mark.parametrize('admin_func_name,kwargs', (
|
||||
('get_config', {'table': None}),
|
||||
('reconfigure', {'table': None, 'shards': None, 'replicas': None}),
|
||||
|
|
|
@ -13,7 +13,7 @@ def _test_additionalproperties(node, path=''):
|
|||
if isinstance(node, dict):
|
||||
if node.get('type') == 'object':
|
||||
assert 'additionalProperties' in node, \
|
||||
("additionalProperties not set at path:" + path)
|
||||
('additionalProperties not set at path:' + path)
|
||||
for name, val in node.items():
|
||||
_test_additionalproperties(val, path + name + '.')
|
||||
|
||||
|
@ -47,7 +47,7 @@ def test_drop_descriptions():
|
|||
},
|
||||
'definitions': {
|
||||
'wat': {
|
||||
'description': "go"
|
||||
'description': 'go'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -300,7 +300,6 @@ def test_transaction_serialization(user_input, user_output, data):
|
|||
'operation': Transaction.CREATE,
|
||||
'metadata': None,
|
||||
'asset': {
|
||||
'id': tx_id,
|
||||
'data': data,
|
||||
}
|
||||
}
|
||||
|
@ -308,7 +307,7 @@ def test_transaction_serialization(user_input, user_output, data):
|
|||
tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
|
||||
[user_output])
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict['id'] = tx_dict['asset']['id'] = tx_id
|
||||
tx_dict['id'] = tx_id
|
||||
|
||||
assert tx_dict == expected
|
||||
|
||||
|
@ -335,7 +334,6 @@ def test_transaction_deserialization(user_input, user_output, data):
|
|||
}
|
||||
tx_no_signatures = Transaction._remove_signatures(tx)
|
||||
tx['id'] = Transaction._to_hash(Transaction._to_str(tx_no_signatures))
|
||||
tx['asset']['id'] = tx['id']
|
||||
tx = Transaction.from_dict(tx)
|
||||
|
||||
assert tx == expected
|
||||
|
@ -436,6 +434,15 @@ def test_cast_transaction_link_to_boolean():
|
|||
assert bool(TransactionLink(False, False)) is True
|
||||
|
||||
|
||||
def test_transaction_link_eq():
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
|
||||
assert TransactionLink(1, 2) == TransactionLink(1, 2)
|
||||
assert TransactionLink(2, 2) != TransactionLink(1, 2)
|
||||
assert TransactionLink(1, 1) != TransactionLink(1, 2)
|
||||
assert TransactionLink(2, 1) != TransactionLink(1, 2)
|
||||
|
||||
|
||||
def test_add_input_to_tx(user_input, asset_definition):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
|
||||
|
@ -682,7 +689,6 @@ def test_create_create_transaction_single_io(user_output, user_pub, data):
|
|||
tx_dict = tx.to_dict()
|
||||
tx_dict['inputs'][0]['fulfillment'] = None
|
||||
tx_dict.pop('id')
|
||||
tx_dict['asset'].pop('id')
|
||||
|
||||
assert tx_dict == expected
|
||||
|
||||
|
@ -766,7 +772,6 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
|
|||
metadata=data, asset=data)
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict.pop('id')
|
||||
tx_dict['asset'].pop('id')
|
||||
tx_dict['inputs'][0]['fulfillment'] = None
|
||||
|
||||
assert tx_dict == expected
|
||||
|
@ -966,11 +971,13 @@ def test_cant_add_empty_input():
|
|||
|
||||
|
||||
def test_validate_version(utx):
|
||||
import re
|
||||
import bigchaindb.version
|
||||
from .utils import validate_transaction_model
|
||||
from bigchaindb.common.exceptions import SchemaValidationError
|
||||
|
||||
assert utx.version == bigchaindb.version.__version__
|
||||
short_ver = bigchaindb.version.__short_version__
|
||||
assert utx.version == re.match(r'^(.*\d)', short_ver).group(1)
|
||||
|
||||
validate_transaction_model(utx)
|
||||
|
||||
|
@ -978,25 +985,3 @@ def test_validate_version(utx):
|
|||
utx.version = '1.0.0'
|
||||
with raises(SchemaValidationError):
|
||||
validate_transaction_model(utx)
|
||||
|
||||
|
||||
def test_create_tx_has_asset_id(tx):
|
||||
tx = tx.to_dict()
|
||||
assert tx['id'] == tx['asset']['id']
|
||||
|
||||
|
||||
def test_create_tx_validates_asset_id(tx):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.exceptions import InvalidHash
|
||||
|
||||
tx = tx.to_dict()
|
||||
|
||||
# Test fails with wrong asset_id
|
||||
tx['asset']['id'] = tx['asset']['id'][::-1]
|
||||
with raises(InvalidHash):
|
||||
Transaction.from_dict(tx)
|
||||
|
||||
# Test fails with no asset_id
|
||||
tx['asset'].pop('id')
|
||||
with raises(InvalidHash):
|
||||
Transaction.from_dict(tx)
|
||||
|
|
|
@ -118,9 +118,8 @@ def _configure_bigchaindb(request):
|
|||
test_db_name = '{}_{}'.format(TEST_DB_NAME, xdist_suffix)
|
||||
|
||||
backend = request.config.getoption('--database-backend')
|
||||
backend_conf = getattr(bigchaindb, '_database_' + backend)
|
||||
config = {
|
||||
'database': backend_conf,
|
||||
'database': bigchaindb._database_map[backend],
|
||||
'keypair': {
|
||||
'private': '31Lb1ZGKTyHnmVK3LUMrAUrPNfd4sE2YyBt3UA4A25aA',
|
||||
'public': '4XYfCbabAWVUCbjTmRTFEu2sc3dFEdkse4r6X498B1s8',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from time import sleep
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
pytestmark = pytest.mark.bdb
|
||||
|
||||
|
@ -1156,3 +1157,68 @@ class TestMultipleInputs(object):
|
|||
# check that the other remain marked as unspent
|
||||
for unspent in transactions[1:]:
|
||||
assert b.get_spent(unspent.id, 0) is None
|
||||
|
||||
|
||||
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()
|
||||
res = b.get_owned_ids('abc')
|
||||
gof.assert_called_once_with('abc', include_spent=False)
|
||||
assert res == gof()
|
||||
|
||||
|
||||
def test_get_outputs_filtered_only_unspent():
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
from bigchaindb.core import Bigchain
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs') as get_outputs:
|
||||
get_outputs.return_value = [TransactionLink('a', 1),
|
||||
TransactionLink('b', 2)]
|
||||
with patch('bigchaindb.core.Bigchain.get_spent') as get_spent:
|
||||
get_spent.side_effect = [True, False]
|
||||
out = Bigchain().get_outputs_filtered('abc', include_spent=False)
|
||||
get_outputs.assert_called_once_with('abc')
|
||||
assert out == [TransactionLink('b', 2)]
|
||||
|
||||
|
||||
def test_get_outputs_filtered():
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
from bigchaindb.core import Bigchain
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs') as get_outputs:
|
||||
get_outputs.return_value = [TransactionLink('a', 1),
|
||||
TransactionLink('b', 2)]
|
||||
with patch('bigchaindb.core.Bigchain.get_spent') as get_spent:
|
||||
out = Bigchain().get_outputs_filtered('abc')
|
||||
get_outputs.assert_called_once_with('abc')
|
||||
get_spent.assert_not_called()
|
||||
assert out == get_outputs.return_value
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
def test_cant_spend_same_input_twice_in_tx(b, genesis_block):
|
||||
"""
|
||||
Recreate duplicated fulfillments bug
|
||||
https://github.com/bigchaindb/bigchaindb/issues/1099
|
||||
"""
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.exceptions import DoubleSpend
|
||||
|
||||
# create a divisible asset
|
||||
tx_create = Transaction.create([b.me], [([b.me], 100)])
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
assert b.validate_transaction(tx_create_signed) == tx_create_signed
|
||||
|
||||
# create a block and valid vote
|
||||
block = b.create_block([tx_create_signed])
|
||||
b.write_block(block)
|
||||
vote = b.vote(block.id, genesis_block.id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# Create a transfer transaction with duplicated fulfillments
|
||||
dup_inputs = tx_create.to_inputs() + tx_create.to_inputs()
|
||||
tx_transfer = Transaction.transfer(dup_inputs, [([b.me], 200)],
|
||||
asset_id=tx_create.id)
|
||||
tx_transfer_signed = tx_transfer.sign([b.me_private])
|
||||
assert b.is_valid_transaction(tx_transfer_signed) is False
|
||||
with pytest.raises(DoubleSpend):
|
||||
tx_transfer_signed.validate(b)
|
||||
|
|
|
@ -10,8 +10,13 @@ ORIGINAL_CONFIG = copy.deepcopy(bigchaindb._config)
|
|||
|
||||
|
||||
@pytest.fixture(scope='function', autouse=True)
|
||||
def clean_config(monkeypatch):
|
||||
monkeypatch.setattr('bigchaindb.config', copy.deepcopy(ORIGINAL_CONFIG))
|
||||
def clean_config(monkeypatch, request):
|
||||
|
||||
import bigchaindb
|
||||
original_config = copy.deepcopy(ORIGINAL_CONFIG)
|
||||
backend = request.config.getoption('--database-backend')
|
||||
original_config['database'] = bigchaindb._database_map[backend]
|
||||
monkeypatch.setattr('bigchaindb.config', original_config)
|
||||
|
||||
|
||||
def test_bigchain_instance_is_initialized_when_conf_provided(request):
|
||||
|
@ -23,11 +28,6 @@ def test_bigchain_instance_is_initialized_when_conf_provided(request):
|
|||
|
||||
assert bigchaindb.config['CONFIGURED'] is True
|
||||
|
||||
# set the current backend so that Bigchain can create a connection
|
||||
backend = request.config.getoption('--database-backend')
|
||||
backend_conf = getattr(bigchaindb, '_database_' + backend)
|
||||
bigchaindb.config['database'] = backend_conf
|
||||
|
||||
b = bigchaindb.Bigchain()
|
||||
|
||||
assert b.me
|
||||
|
@ -44,11 +44,6 @@ def test_bigchain_instance_raises_when_not_configured(request, monkeypatch):
|
|||
# from existing configurations
|
||||
monkeypatch.setattr(config_utils, 'autoconfigure', lambda: 0)
|
||||
|
||||
# set the current backend so that Bigchain can create a connection
|
||||
backend = request.config.getoption('--database-backend')
|
||||
backend_conf = getattr(bigchaindb, '_database_' + backend)
|
||||
bigchaindb.config['database'] = backend_conf
|
||||
|
||||
with pytest.raises(exceptions.KeypairNotFoundException):
|
||||
bigchaindb.Bigchain()
|
||||
|
||||
|
@ -114,47 +109,55 @@ def test_env_config(monkeypatch):
|
|||
|
||||
|
||||
def test_autoconfigure_read_both_from_file_and_env(monkeypatch, request):
|
||||
# constants
|
||||
DATABASE_HOST = 'test-host'
|
||||
DATABASE_NAME = 'test-dbname'
|
||||
DATABASE_PORT = 4242
|
||||
DATABASE_BACKEND = request.config.getoption('--database-backend')
|
||||
SERVER_BIND = '1.2.3.4:56'
|
||||
KEYRING = 'pubkey_0:pubkey_1:pubkey_2'
|
||||
|
||||
file_config = {
|
||||
'database': {
|
||||
'host': 'test-host',
|
||||
'backend': request.config.getoption('--database-backend')
|
||||
'host': DATABASE_HOST
|
||||
},
|
||||
'backlog_reassign_delay': 5
|
||||
}
|
||||
monkeypatch.setattr('bigchaindb.config_utils.file_config', lambda *args, **kwargs: file_config)
|
||||
monkeypatch.setattr('os.environ', {'BIGCHAINDB_DATABASE_NAME': 'test-dbname',
|
||||
'BIGCHAINDB_DATABASE_PORT': '4242',
|
||||
'BIGCHAINDB_SERVER_BIND': '1.2.3.4:56',
|
||||
'BIGCHAINDB_KEYRING': 'pubkey_0:pubkey_1:pubkey_2'})
|
||||
monkeypatch.setattr('os.environ', {'BIGCHAINDB_DATABASE_NAME': DATABASE_NAME,
|
||||
'BIGCHAINDB_DATABASE_PORT': str(DATABASE_PORT),
|
||||
'BIGCHAINDB_DATABASE_BACKEND': DATABASE_BACKEND,
|
||||
'BIGCHAINDB_SERVER_BIND': SERVER_BIND,
|
||||
'BIGCHAINDB_KEYRING': KEYRING})
|
||||
|
||||
import bigchaindb
|
||||
from bigchaindb import config_utils
|
||||
config_utils.autoconfigure()
|
||||
|
||||
backend = request.config.getoption('--database-backend')
|
||||
database_rethinkdb = {
|
||||
'backend': 'rethinkdb',
|
||||
'host': 'test-host',
|
||||
'port': 4242,
|
||||
'name': 'test-dbname',
|
||||
'host': DATABASE_HOST,
|
||||
'port': DATABASE_PORT,
|
||||
'name': DATABASE_NAME,
|
||||
}
|
||||
database_mongodb = {
|
||||
'backend': 'mongodb',
|
||||
'host': 'test-host',
|
||||
'port': 4242,
|
||||
'name': 'test-dbname',
|
||||
'host': DATABASE_HOST,
|
||||
'port': DATABASE_PORT,
|
||||
'name': DATABASE_NAME,
|
||||
'replicaset': 'bigchain-rs',
|
||||
}
|
||||
|
||||
# default
|
||||
database = database_rethinkdb
|
||||
if backend == 'mongodb':
|
||||
database = {}
|
||||
if DATABASE_BACKEND == 'mongodb':
|
||||
database = database_mongodb
|
||||
elif DATABASE_BACKEND == 'rethinkdb':
|
||||
database = database_rethinkdb
|
||||
|
||||
assert bigchaindb.config == {
|
||||
'CONFIGURED': True,
|
||||
'server': {
|
||||
'bind': '1.2.3.4:56',
|
||||
'bind': SERVER_BIND,
|
||||
'workers': None,
|
||||
'threads': None,
|
||||
},
|
||||
|
@ -163,7 +166,7 @@ def test_autoconfigure_read_both_from_file_and_env(monkeypatch, request):
|
|||
'public': None,
|
||||
'private': None,
|
||||
},
|
||||
'keyring': ['pubkey_0', 'pubkey_1', 'pubkey_2'],
|
||||
'keyring': KEYRING.split(':'),
|
||||
'statsd': {
|
||||
'host': 'localhost',
|
||||
'port': 8125,
|
||||
|
|
65
tests/test_txlist.py
Normal file
65
tests/test_txlist.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
"""
|
||||
Test getting a list of transactions from the backend.
|
||||
|
||||
This test module defines it's own fixture which is used by all the tests.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def txlist(b, user_pk, user2_pk, user_sk, user2_sk, genesis_block):
|
||||
from bigchaindb.models import Transaction
|
||||
prev_block_id = genesis_block.id
|
||||
|
||||
# Create first block with CREATE transactions
|
||||
create1 = Transaction.create([user_pk], [([user2_pk], 6)]) \
|
||||
.sign([user_sk])
|
||||
create2 = Transaction.create([user2_pk],
|
||||
[([user2_pk], 5), ([user_pk], 5)]) \
|
||||
.sign([user2_sk])
|
||||
block1 = b.create_block([create1, create2])
|
||||
b.write_block(block1)
|
||||
|
||||
# Create second block with TRANSFER transactions
|
||||
transfer1 = Transaction.transfer(create1.to_inputs(),
|
||||
[([user_pk], 8)],
|
||||
create1.id).sign([user2_sk])
|
||||
block2 = b.create_block([transfer1])
|
||||
b.write_block(block2)
|
||||
|
||||
# Create block with double spend
|
||||
tx_doublespend = Transaction.transfer(create1.to_inputs(), [([user_pk], 9)],
|
||||
create1.id).sign([user2_sk])
|
||||
block_doublespend = b.create_block([tx_doublespend])
|
||||
b.write_block(block_doublespend)
|
||||
|
||||
# Vote on all the blocks
|
||||
prev_block_id = genesis_block.id
|
||||
for bid in [block1.id, block2.id]:
|
||||
vote = b.vote(bid, prev_block_id, True)
|
||||
prev_block_id = bid
|
||||
b.write_vote(vote)
|
||||
|
||||
# Create undecided block
|
||||
untx = Transaction.create([user_pk], [([user2_pk], 7)]) \
|
||||
.sign([user_sk])
|
||||
block_undecided = b.create_block([untx])
|
||||
b.write_block(block_undecided)
|
||||
|
||||
return type('', (), {
|
||||
'create1': create1,
|
||||
'transfer1': transfer1,
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
def test_get_txlist_by_asset(b, txlist):
|
||||
res = b.get_transactions_filtered(txlist.create1.id)
|
||||
assert set(tx.id for tx in res) == set([txlist.transfer1.id,
|
||||
txlist.create1.id])
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
def test_get_txlist_by_operation(b, txlist):
|
||||
res = b.get_transactions_filtered(txlist.create1.id, operation='CREATE')
|
||||
assert set(tx.id for tx in res) == {txlist.create1.id}
|
|
@ -41,7 +41,7 @@ def test_get_blocks_by_txid_endpoint(b, client):
|
|||
block_invalid = b.create_block([tx])
|
||||
b.write_block(block_invalid)
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=" + tx.id)
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=' + tx.id)
|
||||
# test if block is retrieved as undecided
|
||||
assert res.status_code == 200
|
||||
assert block_invalid.id in res.json
|
||||
|
@ -51,7 +51,7 @@ def test_get_blocks_by_txid_endpoint(b, client):
|
|||
vote = b.vote(block_invalid.id, b.get_last_voted_block().id, False)
|
||||
b.write_vote(vote)
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=" + tx.id)
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=' + tx.id)
|
||||
# test if block is retrieved as invalid
|
||||
assert res.status_code == 200
|
||||
assert block_invalid.id in res.json
|
||||
|
@ -61,7 +61,7 @@ def test_get_blocks_by_txid_endpoint(b, client):
|
|||
block_valid = b.create_block([tx, tx2])
|
||||
b.write_block(block_valid)
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=" + tx.id)
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=' + tx.id)
|
||||
# test if block is retrieved as undecided
|
||||
assert res.status_code == 200
|
||||
assert block_valid.id in res.json
|
||||
|
@ -71,7 +71,7 @@ def test_get_blocks_by_txid_endpoint(b, client):
|
|||
vote = b.vote(block_valid.id, block_invalid.id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=" + tx.id)
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=' + tx.id)
|
||||
# test if block is retrieved as valid
|
||||
assert res.status_code == 200
|
||||
assert block_valid.id in res.json
|
||||
|
@ -96,19 +96,19 @@ def test_get_blocks_by_txid_and_status_endpoint(b, client):
|
|||
block_valid = b.create_block([tx, tx2])
|
||||
b.write_block(block_valid)
|
||||
|
||||
res = client.get("{}?tx_id={}&status={}".format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_INVALID))
|
||||
res = client.get('{}?tx_id={}&status={}'.format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_INVALID))
|
||||
# test if no blocks are retrieved as invalid
|
||||
assert res.status_code == 200
|
||||
assert len(res.json) == 0
|
||||
|
||||
res = client.get("{}?tx_id={}&status={}".format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_UNDECIDED))
|
||||
res = client.get('{}?tx_id={}&status={}'.format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_UNDECIDED))
|
||||
# test if both blocks are retrieved as undecided
|
||||
assert res.status_code == 200
|
||||
assert block_valid.id in res.json
|
||||
assert block_invalid.id in res.json
|
||||
assert len(res.json) == 2
|
||||
|
||||
res = client.get("{}?tx_id={}&status={}".format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_VALID))
|
||||
res = client.get('{}?tx_id={}&status={}'.format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_VALID))
|
||||
# test if no blocks are retrieved as valid
|
||||
assert res.status_code == 200
|
||||
assert len(res.json) == 0
|
||||
|
@ -121,18 +121,18 @@ def test_get_blocks_by_txid_and_status_endpoint(b, client):
|
|||
vote = b.vote(block_valid.id, block_invalid.id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
res = client.get("{}?tx_id={}&status={}".format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_INVALID))
|
||||
res = client.get('{}?tx_id={}&status={}'.format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_INVALID))
|
||||
# test if the invalid block is retrieved as invalid
|
||||
assert res.status_code == 200
|
||||
assert block_invalid.id in res.json
|
||||
assert len(res.json) == 1
|
||||
|
||||
res = client.get("{}?tx_id={}&status={}".format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_UNDECIDED))
|
||||
res = client.get('{}?tx_id={}&status={}'.format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_UNDECIDED))
|
||||
# test if no blocks are retrieved as undecided
|
||||
assert res.status_code == 200
|
||||
assert len(res.json) == 0
|
||||
|
||||
res = client.get("{}?tx_id={}&status={}".format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_VALID))
|
||||
res = client.get('{}?tx_id={}&status={}'.format(BLOCKS_ENDPOINT, tx.id, Bigchain.BLOCK_VALID))
|
||||
# test if the valid block is retrieved as valid
|
||||
assert res.status_code == 200
|
||||
assert block_valid.id in res.json
|
||||
|
@ -141,11 +141,11 @@ def test_get_blocks_by_txid_and_status_endpoint(b, client):
|
|||
|
||||
@pytest.mark.bdb
|
||||
def test_get_blocks_by_txid_endpoint_returns_empty_list_not_found(client):
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=")
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=')
|
||||
assert res.status_code == 200
|
||||
assert len(res.json) == 0
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=123")
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=123')
|
||||
assert res.status_code == 200
|
||||
assert len(res.json) == 0
|
||||
|
||||
|
@ -155,7 +155,7 @@ def test_get_blocks_by_txid_endpoint_returns_400_bad_query_params(client):
|
|||
res = client.get(BLOCKS_ENDPOINT)
|
||||
assert res.status_code == 400
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?ts_id=123")
|
||||
res = client.get(BLOCKS_ENDPOINT + '?ts_id=123')
|
||||
assert res.status_code == 400
|
||||
assert res.json == {
|
||||
'message': {
|
||||
|
@ -163,13 +163,13 @@ def test_get_blocks_by_txid_endpoint_returns_400_bad_query_params(client):
|
|||
}
|
||||
}
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=123&foo=123")
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=123&foo=123')
|
||||
assert res.status_code == 400
|
||||
assert res.json == {
|
||||
'message': 'Unknown arguments: foo'
|
||||
}
|
||||
|
||||
res = client.get(BLOCKS_ENDPOINT + "?tx_id=123&status=123")
|
||||
res = client.get(BLOCKS_ENDPOINT + '?tx_id=123&status=123')
|
||||
assert res.status_code == 400
|
||||
assert res.json == {
|
||||
'message': {
|
||||
|
|
49
tests/web/test_outputs.py
Normal file
49
tests/web/test_outputs.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')]
|
||||
|
||||
OUTPUTS_ENDPOINT = '/api/v1/outputs/'
|
||||
|
||||
|
||||
def test_get_outputs_endpoint(client, user_pk):
|
||||
m = MagicMock()
|
||||
m.to_uri.side_effect = lambda s: 'a%sb' % s
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||
gof.return_value = [m, m]
|
||||
res = client.get(OUTPUTS_ENDPOINT + '?public_key={}'.format(user_pk))
|
||||
assert res.json == ['a..b', 'a..b']
|
||||
assert res.status_code == 200
|
||||
gof.assert_called_once_with(user_pk, True)
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_unspent(client, user_pk):
|
||||
m = MagicMock()
|
||||
m.to_uri.side_effect = lambda s: 'a%sb' % s
|
||||
with patch('bigchaindb.core.Bigchain.get_outputs_filtered') as gof:
|
||||
gof.return_value = [m]
|
||||
params = '?unspent=true&public_key={}'.format(user_pk)
|
||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||
assert res.json == ['a..b']
|
||||
assert res.status_code == 200
|
||||
gof.assert_called_once_with(user_pk, False)
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_without_public_key(client):
|
||||
res = client.get(OUTPUTS_ENDPOINT)
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_with_invalid_public_key(client):
|
||||
expected = {'message': {'public_key': 'Invalid base58 ed25519 key'}}
|
||||
res = client.get(OUTPUTS_ENDPOINT + '?public_key=abc')
|
||||
assert expected == res.json
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_get_outputs_endpoint_with_invalid_unspent(client, user_pk):
|
||||
expected = {'message': {'unspent': 'Boolean value must be "true" or "false" (lowercase)'}}
|
||||
params = '?unspent=tru&public_key={}'.format(user_pk)
|
||||
res = client.get(OUTPUTS_ENDPOINT + params)
|
||||
assert expected == res.json
|
||||
assert res.status_code == 400
|
75
tests/web/test_parameters.py
Normal file
75
tests/web/test_parameters.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
import pytest
|
||||
|
||||
|
||||
def test_valid_txid():
|
||||
from bigchaindb.web.views.parameters import valid_txid
|
||||
|
||||
valid = ['18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4',
|
||||
'18AC3E7343F016890C510E93F935261169D9E3F565436429830FAF0934F4F8E4']
|
||||
for h in valid:
|
||||
assert valid_txid(h) == h.lower()
|
||||
|
||||
non = ['18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e',
|
||||
'18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e45',
|
||||
'18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8eg',
|
||||
'18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e ',
|
||||
'']
|
||||
for h in non:
|
||||
with pytest.raises(ValueError):
|
||||
valid_txid(h)
|
||||
|
||||
|
||||
def test_valid_bool():
|
||||
from bigchaindb.web.views.parameters import valid_bool
|
||||
|
||||
assert valid_bool('true') is True
|
||||
assert valid_bool('false') is False
|
||||
assert valid_bool('tRUE') is True
|
||||
assert valid_bool('fALSE') is False
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
valid_bool('0')
|
||||
with pytest.raises(ValueError):
|
||||
valid_bool('1')
|
||||
with pytest.raises(ValueError):
|
||||
valid_bool('yes')
|
||||
with pytest.raises(ValueError):
|
||||
valid_bool('no')
|
||||
|
||||
|
||||
def test_valid_ed25519():
|
||||
from bigchaindb.web.views.parameters import valid_ed25519
|
||||
|
||||
valid = ['123456789abcdefghijkmnopqrstuvwxyz1111111111',
|
||||
'123456789ABCDEFGHJKLMNPQRSTUVWXYZ1111111111']
|
||||
for h in valid:
|
||||
assert valid_ed25519(h) == h
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz1111111')
|
||||
with pytest.raises(ValueError):
|
||||
valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz1111111111')
|
||||
with pytest.raises(ValueError):
|
||||
valid_ed25519('123456789abcdefghijkmnopqrstuvwxyz111111111l')
|
||||
with pytest.raises(ValueError):
|
||||
valid_ed25519('123456789abcdefghijkmnopqrstuvwxyz111111111I')
|
||||
with pytest.raises(ValueError):
|
||||
valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz11111111O')
|
||||
with pytest.raises(ValueError):
|
||||
valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz111111110')
|
||||
|
||||
|
||||
def test_valid_operation():
|
||||
from bigchaindb.web.views.parameters import valid_operation
|
||||
|
||||
assert valid_operation('create') == 'CREATE'
|
||||
assert valid_operation('transfer') == 'TRANSFER'
|
||||
assert valid_operation('CREATe') == 'CREATE'
|
||||
assert valid_operation('TRANSFEr') == 'TRANSFER'
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
valid_operation('GENESIS')
|
||||
with pytest.raises(ValueError):
|
||||
valid_operation('blah')
|
||||
with pytest.raises(ValueError):
|
||||
valid_operation('')
|
|
@ -10,15 +10,15 @@ STATUSES_ENDPOINT = '/api/v1/statuses'
|
|||
def test_get_transaction_status_endpoint(b, client, user_pk):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
tx, status = b.get_transaction(input_tx.txid, include_status=True)
|
||||
res = client.get(STATUSES_ENDPOINT + "?tx_id=" + input_tx.txid)
|
||||
res = client.get(STATUSES_ENDPOINT + '?tx_id=' + input_tx.txid)
|
||||
assert status == res.json['status']
|
||||
assert res.json['_links']['tx'] == "/transactions/{}".format(input_tx.txid)
|
||||
assert res.json['_links']['tx'] == '/transactions/{}'.format(input_tx.txid)
|
||||
assert res.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
def test_get_transaction_status_endpoint_returns_404_if_not_found(client):
|
||||
res = client.get(STATUSES_ENDPOINT + "?tx_id=123")
|
||||
res = client.get(STATUSES_ENDPOINT + '?tx_id=123')
|
||||
assert res.status_code == 404
|
||||
|
||||
|
||||
|
@ -32,7 +32,7 @@ def test_get_block_status_endpoint_undecided(b, client):
|
|||
|
||||
status = b.block_election_status(block.id, block.voters)
|
||||
|
||||
res = client.get(STATUSES_ENDPOINT + "?block_id=" + block.id)
|
||||
res = client.get(STATUSES_ENDPOINT + '?block_id=' + block.id)
|
||||
assert status == res.json['status']
|
||||
assert '_links' not in res.json
|
||||
assert res.status_code == 200
|
||||
|
@ -53,7 +53,7 @@ def test_get_block_status_endpoint_valid(b, client):
|
|||
|
||||
status = b.block_election_status(block.id, block.voters)
|
||||
|
||||
res = client.get(STATUSES_ENDPOINT + "?block_id=" + block.id)
|
||||
res = client.get(STATUSES_ENDPOINT + '?block_id=' + block.id)
|
||||
assert status == res.json['status']
|
||||
assert '_links' not in res.json
|
||||
assert res.status_code == 200
|
||||
|
@ -74,7 +74,7 @@ def test_get_block_status_endpoint_invalid(b, client):
|
|||
|
||||
status = b.block_election_status(block.id, block.voters)
|
||||
|
||||
res = client.get(STATUSES_ENDPOINT + "?block_id=" + block.id)
|
||||
res = client.get(STATUSES_ENDPOINT + '?block_id=' + block.id)
|
||||
assert status == res.json['status']
|
||||
assert '_links' not in res.json
|
||||
assert res.status_code == 200
|
||||
|
@ -82,7 +82,7 @@ def test_get_block_status_endpoint_invalid(b, client):
|
|||
|
||||
@pytest.mark.bdb
|
||||
def test_get_block_status_endpoint_returns_404_if_not_found(client):
|
||||
res = client.get(STATUSES_ENDPOINT + "?block_id=123")
|
||||
res = client.get(STATUSES_ENDPOINT + '?block_id=123')
|
||||
assert res.status_code == 404
|
||||
|
||||
|
||||
|
@ -91,8 +91,8 @@ def test_get_status_endpoint_returns_400_bad_query_params(client):
|
|||
res = client.get(STATUSES_ENDPOINT)
|
||||
assert res.status_code == 400
|
||||
|
||||
res = client.get(STATUSES_ENDPOINT + "?ts_id=123")
|
||||
res = client.get(STATUSES_ENDPOINT + '?ts_id=123')
|
||||
assert res.status_code == 400
|
||||
|
||||
res = client.get(STATUSES_ENDPOINT + "?tx_id=123&block_id=123")
|
||||
res = client.get(STATUSES_ENDPOINT + '?tx_id=123&block_id=123')
|
||||
assert res.status_code == 400
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import builtins
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from bigchaindb.common import crypto
|
||||
|
@ -37,6 +38,9 @@ def test_post_create_transaction_endpoint(b, client):
|
|||
tx = tx.sign([user_priv])
|
||||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
|
||||
|
||||
assert res.status_code == 202
|
||||
|
||||
assert res.json['inputs'][0]['owners_before'][0] == user_pub
|
||||
assert res.json['outputs'][0]['public_keys'][0] == user_pub
|
||||
|
||||
|
@ -53,8 +57,8 @@ def test_post_create_transaction_with_invalid_id(b, client, caplog):
|
|||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||
expected_status_code = 400
|
||||
expected_error_message = (
|
||||
"Invalid transaction ({}): The transaction's id '{}' isn't equal to "
|
||||
"the hash of its body, i.e. it's not valid."
|
||||
'Invalid transaction ({}): The transaction\'s id \'{}\' isn\'t equal to '
|
||||
'the hash of its body, i.e. it\'s not valid.'
|
||||
).format(InvalidHash.__name__, tx['id'])
|
||||
assert res.status_code == expected_status_code
|
||||
assert res.json['message'] == expected_error_message
|
||||
|
@ -74,8 +78,8 @@ def test_post_create_transaction_with_invalid_signature(b, client, caplog):
|
|||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||
expected_status_code = 400
|
||||
expected_error_message = (
|
||||
"Invalid transaction ({}): Fulfillment URI "
|
||||
"couldn't been parsed"
|
||||
'Invalid transaction ({}): Fulfillment URI '
|
||||
'couldn\'t been parsed'
|
||||
).format(InvalidSignature.__name__)
|
||||
assert res.status_code == expected_status_code
|
||||
assert res.json['message'] == expected_error_message
|
||||
|
@ -156,6 +160,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
|
|||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
||||
|
||||
assert res.status_code == 202
|
||||
|
||||
assert res.json['inputs'][0]['owners_before'][0] == user_pk
|
||||
assert res.json['outputs'][0]['public_keys'][0] == user_pub
|
||||
|
||||
|
@ -180,3 +186,45 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk):
|
|||
InvalidSignature.__name__, 'Transaction signature is invalid.')
|
||||
assert res.status_code == expected_status_code
|
||||
assert res.json['message'] == expected_error_message
|
||||
|
||||
|
||||
def test_transactions_get_list_good(client):
|
||||
from functools import partial
|
||||
|
||||
def get_txs_patched(conn, **args):
|
||||
""" Patch `get_transactions_filtered` so that rather than return an array
|
||||
of transactions it returns an array of shims with a to_dict() method
|
||||
that reports one of the arguments passed to `get_transactions_filtered`.
|
||||
"""
|
||||
return [type('', (), {'to_dict': partial(lambda a: a, arg)})
|
||||
for arg in sorted(args.items())]
|
||||
|
||||
asset_id = '1' * 64
|
||||
|
||||
with patch('bigchaindb.core.Bigchain.get_transactions_filtered', get_txs_patched):
|
||||
url = TX_ENDPOINT + "?asset_id=" + asset_id
|
||||
assert client.get(url).json == [
|
||||
['asset_id', asset_id],
|
||||
['operation', None]
|
||||
]
|
||||
url = TX_ENDPOINT + "?asset_id=" + asset_id + "&operation=CREATE"
|
||||
assert client.get(url).json == [
|
||||
['asset_id', asset_id],
|
||||
['operation', 'CREATE']
|
||||
]
|
||||
|
||||
|
||||
def test_transactions_get_list_bad(client):
|
||||
def should_not_be_called():
|
||||
assert False
|
||||
with patch('bigchaindb.core.Bigchain.get_transactions_filtered',
|
||||
lambda *_, **__: should_not_be_called()):
|
||||
# Test asset id validated
|
||||
url = TX_ENDPOINT + "?asset_id=" + '1' * 63
|
||||
assert client.get(url).status_code == 400
|
||||
# Test operation validated
|
||||
url = TX_ENDPOINT + "?asset_id=" + '1' * 64 + "&operation=CEATE"
|
||||
assert client.get(url).status_code == 400
|
||||
# Test asset ID required
|
||||
url = TX_ENDPOINT + "?operation=CREATE"
|
||||
assert client.get(url).status_code == 400
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import pytest
|
||||
|
||||
pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')]
|
||||
|
||||
UNSPENTS_ENDPOINT = '/api/v1/unspents/'
|
||||
|
||||
|
||||
def test_get_unspents_endpoint(b, client, user_pk):
|
||||
expected = [u.to_uri('..') for u in b.get_owned_ids(user_pk)]
|
||||
res = client.get(UNSPENTS_ENDPOINT + '?public_key={}'.format(user_pk))
|
||||
assert expected == res.json
|
||||
assert res.status_code == 200
|
||||
|
||||
|
||||
def test_get_unspents_endpoint_without_public_key(client):
|
||||
res = client.get(UNSPENTS_ENDPOINT)
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_get_unspents_endpoint_with_unused_public_key(client):
|
||||
expected = []
|
||||
res = client.get(UNSPENTS_ENDPOINT + '?public_key=abc')
|
||||
assert expected == res.json
|
||||
assert res.status_code == 200
|
|
@ -18,7 +18,7 @@ def test_get_votes_endpoint(b, client):
|
|||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
res = client.get(VOTES_ENDPOINT + "?block_id=" + block.id)
|
||||
res = client.get(VOTES_ENDPOINT + '?block_id=' + block.id)
|
||||
assert vote == res.json[0]
|
||||
assert len(res.json) == 1
|
||||
assert res.status_code == 200
|
||||
|
@ -41,18 +41,18 @@ def test_get_votes_endpoint_multiple_votes(b, client):
|
|||
vote_invalid = b.vote(block.id, last_block, False)
|
||||
b.write_vote(vote_invalid)
|
||||
|
||||
res = client.get(VOTES_ENDPOINT + "?block_id=" + block.id)
|
||||
res = client.get(VOTES_ENDPOINT + '?block_id=' + block.id)
|
||||
assert len(res.json) == 2
|
||||
assert res.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.bdb
|
||||
def test_get_votes_endpoint_returns_empty_list_not_found(client):
|
||||
res = client.get(VOTES_ENDPOINT + "?block_id=")
|
||||
res = client.get(VOTES_ENDPOINT + '?block_id=')
|
||||
assert [] == res.json
|
||||
assert res.status_code == 200
|
||||
|
||||
res = client.get(VOTES_ENDPOINT + "?block_id=123")
|
||||
res = client.get(VOTES_ENDPOINT + '?block_id=123')
|
||||
assert [] == res.json
|
||||
assert res.status_code == 200
|
||||
|
||||
|
@ -62,8 +62,8 @@ def test_get_votes_endpoint_returns_400_bad_query_params(client):
|
|||
res = client.get(VOTES_ENDPOINT)
|
||||
assert res.status_code == 400
|
||||
|
||||
res = client.get(VOTES_ENDPOINT + "?ts_id=123")
|
||||
res = client.get(VOTES_ENDPOINT + '?ts_id=123')
|
||||
assert res.status_code == 400
|
||||
|
||||
res = client.get(VOTES_ENDPOINT + "?tx_id=123&block_id=123")
|
||||
res = client.get(VOTES_ENDPOINT + '?tx_id=123&block_id=123')
|
||||
assert res.status_code == 400
|
||||
|
|
Loading…
Reference in New Issue
Block a user