Merge branch 'master' into divisible-assets

This commit is contained in:
Sylvain Bellemare 2016-11-14 16:39:02 +01:00
commit 9f471ef4b3
7 changed files with 191 additions and 38 deletions

View File

@ -82,7 +82,7 @@ How? Let's split the command down into its components:
- `install` tells pip to use the *install* action
- `-e` installs a project in [editable mode](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs)
- `.` installs what's in the current directory
- `[dev]` adds some [extra requirements](https://pythonhosted.org/setuptools/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) to the installation. (If you are curious, open `setup.py` and look for `dev` in the `extras_require` section.)
- `[dev]` adds some [extra requirements](https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) to the installation. (If you are curious, open `setup.py` and look for `dev` in the `extras_require` section.)
Aside: An alternative to `pip install -e .[dev]` is `python setup.py develop`.

View File

@ -860,20 +860,14 @@ class Transaction(object):
:obj:`list` of :class:`~bigchaindb.common.transaction.
Fulfillment`
"""
inputs = []
if condition_indices is None or len(condition_indices) == 0:
# NOTE: If no condition indices are passed, we just assume to
# take all conditions as inputs.
condition_indices = [index for index, _
in enumerate(self.conditions)]
for cid in condition_indices:
input_cond = self.conditions[cid]
ffill = Fulfillment(input_cond.fulfillment,
input_cond.owners_after,
return [
Fulfillment(self.conditions[cid].fulfillment,
self.conditions[cid].owners_after,
TransactionLink(self.id, cid))
inputs.append(ffill)
return inputs
for cid in condition_indices or range(len(self.conditions))
]
def add_fulfillment(self, fulfillment):
"""Adds a Fulfillment to a Transaction's list of Fulfillments.

View File

@ -189,6 +189,28 @@ class Bigchain(object):
exceptions.FulfillmentNotInValidBlock, exceptions.AmountError):
return False
def get_block(self, block_id, include_status=False):
"""Get the block with the specified `block_id` (and optionally its status)
Returns the block corresponding to `block_id` or None if no match is
found.
Args:
block_id (str): transaction id of the transaction to get
include_status (bool): also return the status of the block
the return value is then a tuple: (block, status)
"""
block = self.backend.get_block(block_id)
status = None
if include_status:
if block:
status = self.block_election_status(block_id,
block['block']['voters'])
return block, status
else:
return block
def get_transaction(self, txid, include_status=False):
"""Get the transaction with the specified `txid` (and optionally its status)

View File

@ -278,6 +278,17 @@ class RethinkDBBackend:
r.table('bigchain')
.insert(r.json(block), durability=durability))
def get_block(self, block_id):
"""Get a block from the bigchain table
Args:
block_id (str): block id of the block to get
Returns:
block (dict): the block or `None`
"""
return self.connection.run(r.table('bigchain').get(block_id))
def has_transaction(self, transaction_id):
"""Check if a transaction exists in the bigchain table.
@ -302,6 +313,17 @@ class RethinkDBBackend:
r.table('bigchain', read_mode=self.read_mode)
.count())
def count_backlog(self):
"""Count the number of transactions in the backlog table.
Returns:
The number of transactions in the backlog.
"""
return self.connection.run(
r.table('backlog', read_mode=self.read_mode)
.count())
def write_vote(self, vote):
"""Write a vote to the votes table.
@ -315,6 +337,17 @@ class RethinkDBBackend:
r.table('votes')
.insert(vote))
def get_genesis_block(self):
"""Get the genesis block
Returns:
The genesis block
"""
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(util.is_genesis_block)
.nth(0))
def get_last_voted_block(self, node_pubkey):
"""Get the last voted block for a specific node.
@ -339,10 +372,7 @@ class RethinkDBBackend:
except r.ReqlNonExistenceError:
# return last vote if last vote exists else return Genesis block
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.filter(util.is_genesis_block)
.nth(0))
return self.get_genesis_block()
# Now the fun starts. Since the resolution of timestamp is a second,
# we might have more than one vote per timestamp. If this is the case

View File

@ -113,8 +113,36 @@ class Transaction(Transaction):
class Block(object):
"""Bundle a list of Transactions in a Block. Nodes vote on its validity.
Attributes:
transaction (:obj:`list` of :class:`~.Transaction`):
Transactions to be included in the Block.
node_pubkey (str): The public key of the node creating the
Block.
timestamp (str): The Unix time a Block was created.
voters (:obj:`list` of :obj:`str`): A list of a federation
nodes' public keys supposed to vote on the Block.
signature (str): A cryptographic signature ensuring the
integrity and validity of the creator of a Block.
"""
def __init__(self, transactions=None, node_pubkey=None, timestamp=None,
voters=None, signature=None):
"""The Block model is mainly used for (de)serialization and integrity
checking.
Args:
transaction (:obj:`list` of :class:`~.Transaction`):
Transactions to be included in the Block.
node_pubkey (str): The public key of the node creating the
Block.
timestamp (str): The Unix time a Block was created.
voters (:obj:`list` of :obj:`str`): A list of a federation
nodes' public keys supposed to vote on the Block.
signature (str): A cryptographic signature ensuring the
integrity and validity of the creator of a Block.
"""
if transactions is not None and not isinstance(transactions, list):
raise TypeError('`transactions` must be a list instance or None')
else:
@ -141,18 +169,20 @@ class Block(object):
return self.to_dict() == other
def validate(self, bigchain):
"""Validate a block.
"""Validate the Block.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
bigchain (:class:`~bigchaindb.Bigchain`): An instantiated Bigchain
object.
Returns:
block (Block): The block as a `Block` object if it is valid.
Else it raises an appropriate exception describing
the reason of invalidity.
:class:`~.Block`: If valid, return a `Block` object. Else an
appropriate exception describing the reason of invalidity is
raised.
Raises:
OperationError: if a non-federation node signed the block.
OperationError: If a non-federation node signed the Block.
InvalidSignature: If a Block's signature is invalid.
"""
# First, make sure this node hasn't already voted on this block
@ -177,6 +207,15 @@ class Block(object):
return self
def sign(self, signing_key):
"""Create a signature for the Block and overwrite `self.signature`.
Args:
signing_key (str): A signing key corresponding to
`self.node_pubkey`.
Returns:
:class:`~.Block`
"""
block_body = self.to_dict()
block_serialized = serialize(block_body['block'])
signing_key = SigningKey(signing_key)
@ -184,8 +223,13 @@ class Block(object):
return self
def is_signature_valid(self):
"""Check the validity of a Block's signature.
Returns:
bool: Stating the validity of the Block's signature.
"""
block = self.to_dict()['block']
# cc only accepts bytesting messages
# cc only accepts bytestring messages
block_serialized = serialize(block).encode()
verifying_key = VerifyingKey(block['node_pubkey'])
try:
@ -197,6 +241,21 @@ class Block(object):
@classmethod
def from_dict(cls, block_body):
"""Transform a Python dictionary to a Block object.
Args:
block_body (dict): A block dictionary to be transformed.
Returns:
:class:`~Block`
Raises:
InvalidHash: If the block's id is not corresponding to its
data.
InvalidSignature: If the block's signature is not corresponding
to it's data or `node_pubkey`.
"""
# TODO: Reuse `is_signature_valid` method here.
block = block_body['block']
block_serialized = serialize(block)
block_id = hash_data(block_serialized)
@ -232,6 +291,14 @@ class Block(object):
return self.to_dict()['id']
def to_dict(self):
"""Transform the Block to a Python dictionary.
Returns:
dict: The Block as a dict.
Raises:
OperationError: If the Block doesn't contain any transactions.
"""
if len(self.transactions) == 0:
raise OperationError('Empty block creation is not allowed')

View File

@ -3,21 +3,35 @@ 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 querying capabilities in the future.
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
querying capabilities in the future.
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at the address stored in the BigchainDB node configuration settings. The default is:
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at
the address stored in the BigchainDB node configuration settings. The default
is:
`http://localhost:9984/api/v1/ <http://localhost:9984/api/v1/>`_
but that address can be changed by changing the "API endpoint" configuration setting (e.g. in a local config file). There's more information about setting the API endpoint in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
but that address can be changed by changing the "API endpoint" configuration
setting (e.g. in a local config file). There's more information about setting
the API endpoint in :doc:`the section about BigchainDB Configuration Settings
<../server-reference/configuration>`.
There are other configuration settings related to the web server (serving the HTTP API). In particular, the default is for the web server socket to bind to ``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more details, see the "server" settings ("bind", "workers" and "threads") in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
There are other configuration settings related to the web server (serving the
HTTP API). In particular, the default is for the web server socket to bind to
``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more
details, see the "server" settings ("bind", "workers" and "threads") in
:doc:`the section about BigchainDB Configuration Settings
<../server-reference/configuration>`.
API Root
--------
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no ``/api/v1/`` on the end), then you should get an HTTP response with something like the following in the body:
If you send an HTTP GET request to e.g. ``http://localhost: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
@ -40,7 +54,12 @@ POST /transactions/
Push a new transaction.
Note: The posted transaction should be valid `transaction <https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_. 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.
Note: The posted transaction should be valid `transaction
<https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_.
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**:
@ -158,9 +177,11 @@ GET /transactions/{tx_id}/status
.. 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.
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``.
The possible status values are ``backlog``, ``undecided``, ``valid`` or
``invalid``.
:param tx_id: transaction ID
:type tx_id: hex string
@ -194,7 +215,8 @@ GET /transactions/{tx_id}
Get the transaction with the ID ``tx_id``.
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED`` block on ``bigchain``, if exists.
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED``
block on ``bigchain``, if exists.
:param tx_id: transaction ID
:type tx_id: hex string

View File

@ -371,6 +371,15 @@ class TestBigchainApi(object):
assert excinfo.value.args[0] == 'Empty block creation is not allowed'
@pytest.mark.usefixtures('inputs')
def test_get_block_by_id(self, b):
new_block = dummy_block()
b.write_block(new_block, durability='hard')
assert b.get_block(new_block.id) == new_block.to_dict()
block, status = b.get_block(new_block.id, include_status=True)
assert status == b.BLOCK_UNDECIDED
def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b):
import rethinkdb as r
from bigchaindb import util
@ -585,6 +594,15 @@ class TestBigchainApi(object):
with pytest.raises(TransactionDoesNotExist):
tx.validate(Bigchain())
def test_count_backlog(self, b, user_vk):
from bigchaindb.models import Transaction
for _ in range(4):
tx = Transaction.create([b.me], [user_vk]).sign([b.me_private])
b.write_transaction(tx)
assert b.backend.count_backlog() == 4
class TestTransactionValidation(object):
def test_create_operation_with_inputs(self, b, user_vk, create_tx):