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
|
- `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)
|
- `-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
|
- `.` 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`.
|
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.
|
:obj:`list` of :class:`~bigchaindb.common.transaction.
|
||||||
Fulfillment`
|
Fulfillment`
|
||||||
"""
|
"""
|
||||||
inputs = []
|
|
||||||
if condition_indices is None or len(condition_indices) == 0:
|
|
||||||
# NOTE: If no condition indices are passed, we just assume to
|
# NOTE: If no condition indices are passed, we just assume to
|
||||||
# take all conditions as inputs.
|
# take all conditions as inputs.
|
||||||
condition_indices = [index for index, _
|
return [
|
||||||
in enumerate(self.conditions)]
|
Fulfillment(self.conditions[cid].fulfillment,
|
||||||
|
self.conditions[cid].owners_after,
|
||||||
for cid in condition_indices:
|
|
||||||
input_cond = self.conditions[cid]
|
|
||||||
ffill = Fulfillment(input_cond.fulfillment,
|
|
||||||
input_cond.owners_after,
|
|
||||||
TransactionLink(self.id, cid))
|
TransactionLink(self.id, cid))
|
||||||
inputs.append(ffill)
|
for cid in condition_indices or range(len(self.conditions))
|
||||||
return inputs
|
]
|
||||||
|
|
||||||
def add_fulfillment(self, fulfillment):
|
def add_fulfillment(self, fulfillment):
|
||||||
"""Adds a Fulfillment to a Transaction's list of Fulfillments.
|
"""Adds a Fulfillment to a Transaction's list of Fulfillments.
|
||||||
|
@ -189,6 +189,28 @@ class Bigchain(object):
|
|||||||
exceptions.FulfillmentNotInValidBlock, exceptions.AmountError):
|
exceptions.FulfillmentNotInValidBlock, exceptions.AmountError):
|
||||||
return False
|
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):
|
def get_transaction(self, txid, include_status=False):
|
||||||
"""Get the transaction with the specified `txid` (and optionally its status)
|
"""Get the transaction with the specified `txid` (and optionally its status)
|
||||||
|
|
||||||
|
@ -278,6 +278,17 @@ class RethinkDBBackend:
|
|||||||
r.table('bigchain')
|
r.table('bigchain')
|
||||||
.insert(r.json(block), durability=durability))
|
.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):
|
def has_transaction(self, transaction_id):
|
||||||
"""Check if a transaction exists in the bigchain table.
|
"""Check if a transaction exists in the bigchain table.
|
||||||
|
|
||||||
@ -302,6 +313,17 @@ class RethinkDBBackend:
|
|||||||
r.table('bigchain', read_mode=self.read_mode)
|
r.table('bigchain', read_mode=self.read_mode)
|
||||||
.count())
|
.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):
|
def write_vote(self, vote):
|
||||||
"""Write a vote to the votes table.
|
"""Write a vote to the votes table.
|
||||||
|
|
||||||
@ -315,6 +337,17 @@ class RethinkDBBackend:
|
|||||||
r.table('votes')
|
r.table('votes')
|
||||||
.insert(vote))
|
.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):
|
def get_last_voted_block(self, node_pubkey):
|
||||||
"""Get the last voted block for a specific node.
|
"""Get the last voted block for a specific node.
|
||||||
|
|
||||||
@ -339,10 +372,7 @@ class RethinkDBBackend:
|
|||||||
|
|
||||||
except r.ReqlNonExistenceError:
|
except r.ReqlNonExistenceError:
|
||||||
# return last vote if last vote exists else return Genesis block
|
# return last vote if last vote exists else return Genesis block
|
||||||
return self.connection.run(
|
return self.get_genesis_block()
|
||||||
r.table('bigchain', read_mode=self.read_mode)
|
|
||||||
.filter(util.is_genesis_block)
|
|
||||||
.nth(0))
|
|
||||||
|
|
||||||
# Now the fun starts. Since the resolution of timestamp is a second,
|
# 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
|
# we might have more than one vote per timestamp. If this is the case
|
||||||
|
@ -113,8 +113,36 @@ class Transaction(Transaction):
|
|||||||
|
|
||||||
|
|
||||||
class Block(object):
|
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,
|
def __init__(self, transactions=None, node_pubkey=None, timestamp=None,
|
||||||
voters=None, signature=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):
|
if transactions is not None and not isinstance(transactions, list):
|
||||||
raise TypeError('`transactions` must be a list instance or None')
|
raise TypeError('`transactions` must be a list instance or None')
|
||||||
else:
|
else:
|
||||||
@ -141,18 +169,20 @@ class Block(object):
|
|||||||
return self.to_dict() == other
|
return self.to_dict() == other
|
||||||
|
|
||||||
def validate(self, bigchain):
|
def validate(self, bigchain):
|
||||||
"""Validate a block.
|
"""Validate the Block.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
|
bigchain (:class:`~bigchaindb.Bigchain`): An instantiated Bigchain
|
||||||
|
object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
block (Block): The block as a `Block` object if it is valid.
|
:class:`~.Block`: If valid, return a `Block` object. Else an
|
||||||
Else it raises an appropriate exception describing
|
appropriate exception describing the reason of invalidity is
|
||||||
the reason of invalidity.
|
raised.
|
||||||
|
|
||||||
Raises:
|
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
|
# First, make sure this node hasn't already voted on this block
|
||||||
@ -177,6 +207,15 @@ class Block(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def sign(self, signing_key):
|
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_body = self.to_dict()
|
||||||
block_serialized = serialize(block_body['block'])
|
block_serialized = serialize(block_body['block'])
|
||||||
signing_key = SigningKey(signing_key)
|
signing_key = SigningKey(signing_key)
|
||||||
@ -184,8 +223,13 @@ class Block(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def is_signature_valid(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']
|
block = self.to_dict()['block']
|
||||||
# cc only accepts bytesting messages
|
# cc only accepts bytestring messages
|
||||||
block_serialized = serialize(block).encode()
|
block_serialized = serialize(block).encode()
|
||||||
verifying_key = VerifyingKey(block['node_pubkey'])
|
verifying_key = VerifyingKey(block['node_pubkey'])
|
||||||
try:
|
try:
|
||||||
@ -197,6 +241,21 @@ class Block(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, block_body):
|
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 = block_body['block']
|
||||||
block_serialized = serialize(block)
|
block_serialized = serialize(block)
|
||||||
block_id = hash_data(block_serialized)
|
block_id = hash_data(block_serialized)
|
||||||
@ -232,6 +291,14 @@ class Block(object):
|
|||||||
return self.to_dict()['id']
|
return self.to_dict()['id']
|
||||||
|
|
||||||
def to_dict(self):
|
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:
|
if len(self.transactions) == 0:
|
||||||
raise OperationError('Empty block creation is not allowed')
|
raise OperationError('Empty block creation is not allowed')
|
||||||
|
|
||||||
|
@ -3,21 +3,35 @@ The HTTP Client-Server API
|
|||||||
|
|
||||||
.. note::
|
.. 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/>`_
|
`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
|
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
|
.. code-block:: json
|
||||||
|
|
||||||
@ -40,7 +54,12 @@ POST /transactions/
|
|||||||
|
|
||||||
Push a new transaction.
|
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**:
|
**Example request**:
|
||||||
|
|
||||||
@ -158,9 +177,11 @@ GET /transactions/{tx_id}/status
|
|||||||
|
|
||||||
.. http: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
|
:param tx_id: transaction ID
|
||||||
:type tx_id: hex string
|
:type tx_id: hex string
|
||||||
@ -194,7 +215,8 @@ GET /transactions/{tx_id}
|
|||||||
|
|
||||||
Get the transaction with the ID ``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
|
:param tx_id: transaction ID
|
||||||
:type tx_id: hex string
|
:type tx_id: hex string
|
||||||
|
@ -371,6 +371,15 @@ class TestBigchainApi(object):
|
|||||||
|
|
||||||
assert excinfo.value.args[0] == 'Empty block creation is not allowed'
|
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):
|
def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b):
|
||||||
import rethinkdb as r
|
import rethinkdb as r
|
||||||
from bigchaindb import util
|
from bigchaindb import util
|
||||||
@ -585,6 +594,15 @@ class TestBigchainApi(object):
|
|||||||
with pytest.raises(TransactionDoesNotExist):
|
with pytest.raises(TransactionDoesNotExist):
|
||||||
tx.validate(Bigchain())
|
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):
|
class TestTransactionValidation(object):
|
||||||
def test_create_operation_with_inputs(self, b, user_vk, create_tx):
|
def test_create_operation_with_inputs(self, b, user_vk, create_tx):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user