mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge branch 'master' into divisible-assets
This commit is contained in:
commit
9f471ef4b3
@ -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`.
|
||||
|
||||
|
@ -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,
|
||||
TransactionLink(self.id, cid))
|
||||
inputs.append(ffill)
|
||||
return inputs
|
||||
# NOTE: If no condition indices are passed, we just assume to
|
||||
# take all conditions as inputs.
|
||||
return [
|
||||
Fulfillment(self.conditions[cid].fulfillment,
|
||||
self.conditions[cid].owners_after,
|
||||
TransactionLink(self.id, cid))
|
||||
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.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
@ -215,7 +274,7 @@ class Block(object):
|
||||
# https://github.com/bigchaindb/cryptoconditions/issues/27
|
||||
try:
|
||||
signature_valid = verifying_key\
|
||||
.verify(block_serialized.encode(), signature)
|
||||
.verify(block_serialized.encode(), signature)
|
||||
except ValueError:
|
||||
signature_valid = False
|
||||
if signature_valid is False:
|
||||
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user