Merge branch 'develop' into update-roadmap-md

This commit is contained in:
Troy McConaghy 2016-03-19 10:13:39 +01:00
commit c75fbb8c24
10 changed files with 450 additions and 80 deletions

View File

@ -40,7 +40,8 @@ config = {
'port': e('BIGCHAIN_STATSD_PORT', default=8125), 'port': e('BIGCHAIN_STATSD_PORT', default=8125),
'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01) 'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01)
}, },
'api_endpoint': 'http://localhost:8008/api/v1' 'api_endpoint': 'http://localhost:8008/api/v1',
'consensus_plugin': e('BIGCHAIN_CONSENSUS_PLUGIN', default='default')
} }
# We need to maintain a backup copy of the original config dict in case # We need to maintain a backup copy of the original config dict in case
@ -48,4 +49,3 @@ config = {
# for more info. # for more info.
_config = copy.deepcopy(config) _config = copy.deepcopy(config)
from bigchaindb.core import Bigchain # noqa from bigchaindb.core import Bigchain # noqa

View File

@ -15,7 +15,8 @@ class Client:
In the future, a Client might connect to >1 hosts. In the future, a Client might connect to >1 hosts.
""" """
def __init__(self, public_key=None, private_key=None, api_endpoint=None): def __init__(self, public_key=None, private_key=None, api_endpoint=None,
consensus_plugin=None):
"""Initialize the Client instance """Initialize the Client instance
There are three ways in which the Client instance can get its parameters. There are three ways in which the Client instance can get its parameters.
@ -28,8 +29,11 @@ class Client:
Args: Args:
public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve. public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve.
private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve. private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve.
host (str): hostname where the rethinkdb is running. api_endpoint (str): a URL where rethinkdb is running.
port (int): port in which rethinkb is running (usually 28015). format: scheme://hostname:port
consensus_plugin (str): the registered name of your installed
consensus plugin. The `core` plugin is built into BigchainDB;
others must be installed via pip.
""" """
config_utils.autoconfigure() config_utils.autoconfigure()
@ -37,6 +41,7 @@ class Client:
self.public_key = public_key or bigchaindb.config['keypair']['public'] self.public_key = public_key or bigchaindb.config['keypair']['public']
self.private_key = private_key or bigchaindb.config['keypair']['private'] self.private_key = private_key or bigchaindb.config['keypair']['private']
self.api_endpoint = api_endpoint or bigchaindb.config['api_endpoint'] self.api_endpoint = api_endpoint or bigchaindb.config['api_endpoint']
self.consensus = config_utils.load_consensus_plugin(consensus_plugin)
if not self.public_key or not self.private_key: if not self.public_key or not self.private_key:
raise exceptions.KeypairNotFoundException() raise exceptions.KeypairNotFoundException()
@ -51,8 +56,15 @@ class Client:
The transaction pushed to the Federation. The transaction pushed to the Federation.
""" """
tx = util.create_tx(self.public_key, self.public_key, None, operation='CREATE', payload=payload) tx = self.consensus.create_transaction(
signed_tx = util.sign_tx(tx, self.private_key) current_owner=self.public_key,
new_owner=self.public_key,
tx_input=None,
operation='CREATE',
payload=payload)
signed_tx = self.consensus.sign_transaction(
tx, private_key=self.private_key)
return self._push(signed_tx) return self._push(signed_tx)
def transfer(self, new_owner, tx_input, payload=None): def transfer(self, new_owner, tx_input, payload=None):
@ -67,8 +79,15 @@ class Client:
The transaction pushed to the Federation. The transaction pushed to the Federation.
""" """
tx = util.create_tx(self.public_key, new_owner, tx_input, operation='TRANSFER', payload=payload) tx = self.consensus.create_transaction(
signed_tx = util.sign_tx(tx, self.private_key) current_owner=self.public_key,
new_owner=new_owner,
tx_input=tx_input,
operation='TRANSFER',
payload=payload)
signed_tx = self.consensus.sign_transaction(
tx, private_key=self.private_key)
return self._push(signed_tx) return self._push(signed_tx)
def _push(self, tx): def _push(self, tx):

View File

@ -17,7 +17,10 @@ import json
import logging import logging
import collections import collections
from pkg_resources import iter_entry_points, ResolutionError
import bigchaindb import bigchaindb
from bigchaindb.consensus import AbstractConsensusRules
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CONFIG_DEFAULT_PATH = os.environ.setdefault( CONFIG_DEFAULT_PATH = os.environ.setdefault(
@ -100,3 +103,38 @@ def autoconfigure():
except FileNotFoundError: except FileNotFoundError:
logger.warning('Cannot find your config file. Run `bigchaindb configure` to create one') logger.warning('Cannot find your config file. Run `bigchaindb configure` to create one')
def load_consensus_plugin(name=None):
"""Find and load the chosen consensus plugin.
Args:
name (string): the name of the entry_point, as advertised in the
setup.py of the providing package.
Returns:
an uninstantiated subclass of ``bigchaindb.consensus.AbstractConsensusRules``
"""
if not name:
name = bigchaindb.config.get('consensus_plugin', 'default')
# TODO: This will return the first plugin with group `bigchaindb.consensus`
# and name `name` in the active WorkingSet.
# We should probably support Requirements specs in the config, e.g.
# consensus_plugin: 'my-plugin-package==0.0.1;default'
plugin = None
for entry_point in iter_entry_points('bigchaindb.consensus', name):
plugin = entry_point.load()
# No matching entry_point found
if not plugin:
raise ResolutionError(
'No plugin found in group `bigchaindb.consensus` with name `{}`'.
format(name))
# Is this strictness desireable?
# It will probably reduce developer headaches in the wild.
if not issubclass(plugin, (AbstractConsensusRules)):
raise TypeError("object of type '{}' does not implement `bigchaindb."
"consensus.AbstractConsensusRules`".format(type(plugin)))
return plugin

221
bigchaindb/consensus.py Normal file
View File

@ -0,0 +1,221 @@
from abc import ABCMeta, abstractmethod
import bigchaindb.exceptions as exceptions
from bigchaindb import util
from bigchaindb.crypto import hash_data, PublicKey
class AbstractConsensusRules(metaclass=ABCMeta):
"""Abstract base class for Bigchain plugins which implement consensus logic.
A consensus plugin must expose a class inheriting from this one via an
entry_point.
All methods listed below must be implemented.
"""
@abstractmethod
def validate_transaction(bigchain, transaction):
"""Validate a transaction.
Args:
bigchain (Bigchain): an instantiated ``bigchaindb.Bigchain`` object.
transaction (dict): transaction to validate.
Returns:
The transaction if the transaction is valid else it raises an
exception describing the reason why the transaction is invalid.
Raises:
Descriptive exceptions indicating the reason the transaction failed.
See the `exceptions` module for bigchain-native error classes.
"""
raise NotImplementedError
@abstractmethod
def validate_block(bigchain, block):
"""Validate a block.
Args:
bigchain (Bigchain): an instantiated ``bigchaindb.Bigchain`` object.
block (dict): block to validate.
Returns:
The block if the block is valid else it raises an exception
describing the reason why the block is invalid.
Raises:
Descriptive exceptions indicating the reason the block failed.
See the `exceptions` module for bigchain-native error classes.
"""
raise NotImplementedError
@abstractmethod
def create_transaction(*args, **kwargs):
"""Create a new transaction.
Args:
The signature of this method is left to plugin authors to decide.
Returns:
dict: newly constructed transaction.
"""
raise NotImplementedError
@abstractmethod
def sign_transaction(transaction, *args, **kwargs):
"""Sign a transaction.
Args:
transaction (dict): transaction to sign.
any other arguments are left to plugin authors to decide.
Returns:
dict: transaction with any signatures applied.
"""
raise NotImplementedError
@abstractmethod
def verify_signature(signed_transaction):
"""Verify the signature of a transaction.
Args:
signed_transaction (dict): signed transaction to verify
Returns:
bool: True if the transaction's required signature data is present
and correct, False otherwise.
"""
raise NotImplementedError
class BaseConsensusRules(AbstractConsensusRules):
"""Base consensus rules for Bigchain.
This class can be copied or overridden to write your own consensus rules!
"""
@staticmethod
def validate_transaction(bigchain, transaction):
"""Validate a transaction.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
transaction (dict): transaction to validate.
Returns:
The transaction if the transaction is valid else it raises an
exception describing the reason why the transaction is invalid.
Raises:
OperationError: if the transaction operation is not supported
TransactionDoesNotExist: if the input of the transaction is not found
TransactionOwnerError: if the new transaction is using an input it doesn't own
DoubleSpend: if the transaction is a double spend
InvalidHash: if the hash of the transaction is wrong
InvalidSignature: if the signature of the transaction is wrong
"""
# If the operation is CREATE the transaction should have no inputs and
# should be signed by a federation node
if transaction['transaction']['operation'] == 'CREATE':
if transaction['transaction']['input']:
raise ValueError('A CREATE operation has no inputs')
if transaction['transaction']['current_owner'] not in (
bigchain.federation_nodes + [bigchain.me]):
raise exceptions.OperationError(
'Only federation nodes can use the operation `CREATE`')
else:
# check if the input exists, is owned by the current_owner
if not transaction['transaction']['input']:
raise ValueError(
'Only `CREATE` transactions can have null inputs')
tx_input = bigchain.get_transaction(
transaction['transaction']['input'])
if not tx_input:
raise exceptions.TransactionDoesNotExist(
'input `{}` does not exist in the bigchain'.format(
transaction['transaction']['input']))
if (tx_input['transaction']['new_owner'] !=
transaction['transaction']['current_owner']):
raise exceptions.TransactionOwnerError(
'current_owner `{}` does not own the input `{}`'.format(
transaction['transaction']['current_owner'],
transaction['transaction']['input']))
# check if the input was already spent by a transaction other than
# this one.
spent = bigchain.get_spent(tx_input['id'])
if spent and spent['id'] != transaction['id']:
raise exceptions.DoubleSpend(
'input `{}` was already spent'.format(
transaction['transaction']['input']))
# Check hash of the transaction
calculated_hash = hash_data(util.serialize(
transaction['transaction']))
if calculated_hash != transaction['id']:
raise exceptions.InvalidHash()
# Check signature
if not util.verify_signature(transaction):
raise exceptions.InvalidSignature()
return transaction
# TODO: Unsure if a bigchain parameter is really necessary here?
@staticmethod
def validate_block(bigchain, block):
"""Validate a block.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
block (dict): block to validate.
Returns:
The block if the block is valid else it raises an exception
describing the reason why the block is invalid.
Raises:
InvalidHash: if the hash of the block is wrong.
"""
# Check if current hash is correct
calculated_hash = hash_data(util.serialize(block['block']))
if calculated_hash != block['id']:
raise exceptions.InvalidHash()
return block
@staticmethod
def create_transaction(current_owner, new_owner, tx_input, operation,
payload=None):
"""Create a new transaction
Refer to the documentation of ``bigchaindb.util.create_tx``
"""
return util.create_tx(current_owner, new_owner, tx_input, operation,
payload)
@staticmethod
def sign_transaction(transaction, private_key):
"""Sign a transaction
Refer to the documentation of ``bigchaindb.util.sign_tx``
"""
return util.sign_tx(transaction, private_key)
@staticmethod
def verify_signature(signed_transaction):
"""Verify the signature of a transaction.
Refer to the documentation of ``bigchaindb.util.verify_signature``
"""
return util.verify_signature(signed_transaction)

View File

@ -26,7 +26,8 @@ class Bigchain(object):
""" """
def __init__(self, host=None, port=None, dbname=None, def __init__(self, host=None, port=None, dbname=None,
public_key=None, private_key=None, keyring=[]): public_key=None, private_key=None, keyring=[],
consensus_plugin=None):
"""Initialize the Bigchain instance """Initialize the Bigchain instance
There are three ways in which the Bigchain instance can get its parameters. There are three ways in which the Bigchain instance can get its parameters.
@ -52,6 +53,7 @@ class Bigchain(object):
self.me = public_key or bigchaindb.config['keypair']['public'] self.me = public_key or bigchaindb.config['keypair']['public']
self.me_private = private_key or bigchaindb.config['keypair']['private'] self.me_private = private_key or bigchaindb.config['keypair']['private']
self.federation_nodes = keyring or bigchaindb.config['keyring'] self.federation_nodes = keyring or bigchaindb.config['keyring']
self.consensus = config_utils.load_consensus_plugin(consensus_plugin)
if not self.me or not self.me_private: if not self.me or not self.me_private:
raise exceptions.KeypairNotFoundException() raise exceptions.KeypairNotFoundException()
@ -68,38 +70,40 @@ class Bigchain(object):
return r.connect(host=self.host, port=self.port, db=self.dbname) return r.connect(host=self.host, port=self.port, db=self.dbname)
@monitor.timer('create_transaction', rate=bigchaindb.config['statsd']['rate']) @monitor.timer('create_transaction', rate=bigchaindb.config['statsd']['rate'])
def create_transaction(self, current_owner, new_owner, tx_input, operation, payload=None): def create_transaction(self, *args, **kwargs):
"""Create a new transaction """Create a new transaction
Refer to the documentation of ``bigchaindb.util.create_tx`` Refer to the documentation of your consensus plugin.
Returns:
dict: newly constructed transaction.
""" """
return util.create_tx(current_owner, new_owner, tx_input, operation, payload) return self.consensus.create_transaction(*args, **kwargs)
def sign_transaction(self, transaction, private_key): def sign_transaction(self, transaction, *args, **kwargs):
"""Sign a transaction """Sign a transaction
Refer to the documentation of ``bigchaindb.util.sign_tx`` Refer to the documentation of your consensus plugin.
Returns:
dict: transaction with any signatures applied.
""" """
return util.sign_tx(transaction, private_key) return self.consensus.sign_transaction(transaction, *args, **kwargs)
def verify_signature(self, signed_transaction): def verify_signature(self, signed_transaction, *args, **kwargs):
"""Verify the signature of a transaction. """Verify the signature(s) of a transaction.
Refer to the documentation of ``bigchaindb.crypto.verify_signature`` Refer to the documentation of your consensus plugin.
Returns:
bool: True if the transaction's required signature data is present
and correct, False otherwise.
""" """
data = signed_transaction.copy() return self.consensus.verify_signature(
signed_transaction, *args, **kwargs)
# if assignee field in the transaction, remove it
if 'assignee' in data:
data.pop('assignee')
signature = data.pop('signature')
public_key_base58 = signed_transaction['transaction']['current_owner']
public_key = crypto.PublicKey(public_key_base58)
return public_key.verify(util.serialize(data), signature)
@monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']) @monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate'])
def write_transaction(self, signed_transaction, durability='soft'): def write_transaction(self, signed_transaction, durability='soft'):
@ -109,7 +113,7 @@ class Bigchain(object):
it has been validated by the nodes of the federation. it has been validated by the nodes of the federation.
Args: Args:
singed_transaction (dict): transaction with the `signature` included. signed_transaction (dict): transaction with the `signature` included.
Returns: Returns:
dict: database response dict: database response
@ -244,49 +248,11 @@ class Bigchain(object):
transaction (dict): transaction to validate. transaction (dict): transaction to validate.
Returns: Returns:
The transaction if the transaction is valid else it raises and exception The transaction if the transaction is valid else it raises an
describing the reason why the transaction is invalid. exception describing the reason why the transaction is invalid.
Raises:
OperationError: if the transaction operation is not supported
TransactionDoesNotExist: if the input of the transaction is not found
TransactionOwnerError: if the new transaction is using an input it doesn't own
DoubleSpend: if the transaction is a double spend
InvalidHash: if the hash of the transaction is wrong
InvalidSignature: if the signature of the transaction is wrong
""" """
# If the operation is CREATE the transaction should have no inputs and should be signed by a return self.consensus.validate_transaction(self, transaction)
# federation node
if transaction['transaction']['operation'] == 'CREATE':
if transaction['transaction']['input']:
raise ValueError('A CREATE operation has no inputs')
if transaction['transaction']['current_owner'] not in self.federation_nodes + [self.me]:
raise exceptions.OperationError('Only federation nodes can use the operation `CREATE`')
else:
# check if the input exists, is owned by the current_owner
if not transaction['transaction']['input']:
raise ValueError('Only `CREATE` transactions can have null inputs')
tx_input = self.get_transaction(transaction['transaction']['input'])
if not tx_input:
raise exceptions.TransactionDoesNotExist('input `{}` does not exist in the bigchain'.format(
transaction['transaction']['input']))
if tx_input['transaction']['new_owner'] != transaction['transaction']['current_owner']:
raise exceptions.TransactionOwnerError('current_owner `{}` does not own the input `{}`'.format(
transaction['transaction']['current_owner'], transaction['transaction']['input']))
# check if the input was already spent by a transaction other then this one.
spent = self.get_spent(tx_input['id'])
if spent:
if spent['id'] != transaction['id']:
raise exceptions.DoubleSpend('input `{}` was already spent'.format(
transaction['transaction']['input']))
util.check_hash_and_signature(transaction)
return transaction
def is_valid_transaction(self, transaction): def is_valid_transaction(self, transaction):
"""Check whether a transacion is valid or invalid. """Check whether a transacion is valid or invalid.
@ -356,12 +322,11 @@ class Bigchain(object):
describing the reason why the block is invalid. describing the reason why the block is invalid.
""" """
# 1. Check if current hash is correct # First: Run the plugin block validation logic
calculated_hash = crypto.hash_data(util.serialize(block['block'])) self.consensus.validate_block(self, block)
if calculated_hash != block['id']:
raise exceptions.InvalidHash()
# 2. Validate all transactions in the block # Finally: Tentative assumption that every blockchain will want to
# validate all transactions in each block
for transaction in block['block']['transactions']: for transaction in block['block']['transactions']:
if not self.is_valid_transaction(transaction): if not self.is_valid_transaction(transaction):
# this will raise the exception # this will raise the exception
@ -403,6 +368,8 @@ class Bigchain(object):
response = r.table('bigchain').get_all(transaction_id, index='transaction_id').run(self.conn) response = r.table('bigchain').get_all(transaction_id, index='transaction_id').run(self.conn)
return True if len(response.items) > 0 else False return True if len(response.items) > 0 else False
# TODO: Unless we prescribe the signature of create_transaction, this will
# also need to be moved into the plugin API.
def create_genesis_block(self): def create_genesis_block(self):
"""Create the genesis block """Create the genesis block
@ -509,4 +476,3 @@ class Bigchain(object):
unvoted.pop(0) unvoted.pop(0)
return unvoted return unvoted

View File

@ -57,9 +57,10 @@ def create_transaction():
if tx['transaction']['operation'] == 'CREATE': if tx['transaction']['operation'] == 'CREATE':
tx = util.transform_create(tx) tx = util.transform_create(tx)
tx = util.sign_tx(tx, bigchain.me_private) tx = bigchain.consensus.sign_transaction(
tx, private_key=bigchain.me_private)
if not util.verify_signature(tx): if not bigchain.consensus.verify_signature(tx):
val['error'] = 'Invalid transaction signature' val['error'] = 'Invalid transaction signature'
val = bigchain.write_transaction(tx) val = bigchain.write_transaction(tx)

82
docs/source/consensus.md Normal file
View File

@ -0,0 +1,82 @@
# BigchainDB Consensus Plugins
BigchainDB has a pluggable block/transaction validation architecture. The default consensus rules can be extended or replaced entirely.
## Installing a plugin
Plugins can be installed via pip!
```bash
$ pip install bigchaindb-plugin-demo
```
Or using setuptools:
```bash
$ cd bigchaindb-plugin-demo/
$ python setup.py install # (or develop)
```
To activate your plugin, you can either set the `consensus_plugin` field in your config file (usually `~/.bigchaindb`) or by setting the `BIGCHAIN_CONSENSUS_PLUGIN` environement variable to the name of your plugin (see the section on [Packaging a plugin](#packaging-a-plugin) for more about plugin names).
## Plugin API
BigchainDB's [current plugin API](../../bigchaindb/consensus.py) exposes five functions in an `AbstractConsensusRules` class:
```python
validate_transaction(bigchain, transaction)
validate_block(bigchain, block)
create_transaction(*args, **kwargs)
sign_transaction(transaction, *args, **kwargs)
verify_signature(transaction)
```
Together, these functions are sufficient for most customizations. For example:
- Replace the crypto-system with one your hardware can accelerate
- Re-implement an existing protocol
- Delegate validation to another application
- etc...
## Extending BigchainDB behavior
A default installation of BigchainDB will use the rules in the `BaseConsensusRules` class. If you only need to modify this behavior slightly, you can inherit from that class and call `super()` in any methods you change, so long as the return values remain the same.
Here's a quick example of a plugin that adds nonsense rules:
```python
from bigchaindb.consensus import BaseConsensusRules
class SillyConsensusRules(BaseConsensusRules):
@staticmethod
def validate_transaction(bigchain, transaction):
transaction = super().validate_transaction(bigchain, transaction)
# I only like transactions whose timestamps are even.
if transaction['transaction']['timestamp'] % 2 != 0:
raise StandardError("Odd... very odd indeed.")
return transaction
@staticmethod
def validate_block(bigchain, block):
block = super().validate_block(bigchain, block)
# I don't trust Alice, I think she's shady.
if block['block']['node_pubkey'] == '<ALICE_PUBKEY>':
raise StandardError("Alice is shady, everybody ignore her blocks!")
return block
```
## Packaging a plugin
BigchainDB uses [setuptool's entry_point](https://pythonhosted.org/setuptools/setuptools.html) system to provide the plugin functionality. Any custom plugin needs to add this section to the `setup()` call in their `setup.py`:
```python
entry_points={
'bigchaindb.consensus': [
'PLUGIN_NAME=package.module:ConsensusRulesClass'
]
},
```

View File

@ -56,13 +56,21 @@ setup(
'Operating System :: POSIX :: Linux', 'Operating System :: POSIX :: Linux',
], ],
packages=['bigchaindb', 'bigchaindb.commands', 'bigchaindb.db', 'bigchaindb.web'], packages=[
'bigchaindb',
'bigchaindb.commands',
'bigchaindb.db',
'bigchaindb.web'
],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'bigchaindb=bigchaindb.commands.bigchain:main', 'bigchaindb=bigchaindb.commands.bigchain:main',
'bigchaindb-benchmark=bigchaindb.commands.bigchain_benchmark:main' 'bigchaindb-benchmark=bigchaindb.commands.bigchain_benchmark:main'
], ],
'bigchaindb.consensus': [
'default=bigchaindb.consensus:BaseConsensusRules'
]
}, },
install_requires=[ install_requires=[
'rethinkdb==2.2.0.post4', 'rethinkdb==2.2.0.post4',

View File

@ -15,6 +15,7 @@ def config(request, monkeypatch):
}, },
'keyring': [], 'keyring': [],
'CONFIGURED': True, 'CONFIGURED': True,
'consensus_plugin': 'default'
} }
monkeypatch.setattr('bigchaindb.config', config) monkeypatch.setattr('bigchaindb.config', config)
@ -24,6 +25,7 @@ def config(request, monkeypatch):
def test_bigchain_class_default_initialization(config): def test_bigchain_class_default_initialization(config):
from bigchaindb.core import Bigchain from bigchaindb.core import Bigchain
from bigchaindb.consensus import BaseConsensusRules
bigchain = Bigchain() bigchain = Bigchain()
assert bigchain.host == config['database']['host'] assert bigchain.host == config['database']['host']
assert bigchain.port == config['database']['port'] assert bigchain.port == config['database']['port']
@ -31,11 +33,13 @@ def test_bigchain_class_default_initialization(config):
assert bigchain.me == config['keypair']['public'] assert bigchain.me == config['keypair']['public']
assert bigchain.me_private == config['keypair']['private'] assert bigchain.me_private == config['keypair']['private']
assert bigchain.federation_nodes == config['keyring'] assert bigchain.federation_nodes == config['keyring']
assert bigchain.consensus == BaseConsensusRules
assert bigchain._conn is None assert bigchain._conn is None
def test_bigchain_class_initialization_with_parameters(config): def test_bigchain_class_initialization_with_parameters(config):
from bigchaindb.core import Bigchain from bigchaindb.core import Bigchain
from bigchaindb.consensus import BaseConsensusRules
init_kwargs = { init_kwargs = {
'host': 'some_node', 'host': 'some_node',
'port': '12345', 'port': '12345',
@ -43,6 +47,7 @@ def test_bigchain_class_initialization_with_parameters(config):
'public_key': 'white', 'public_key': 'white',
'private_key': 'black', 'private_key': 'black',
'keyring': ['key_one', 'key_two'], 'keyring': ['key_one', 'key_two'],
'consensus_plugin': 'default'
} }
bigchain = Bigchain(**init_kwargs) bigchain = Bigchain(**init_kwargs)
assert bigchain.host == init_kwargs['host'] assert bigchain.host == init_kwargs['host']
@ -51,4 +56,5 @@ def test_bigchain_class_initialization_with_parameters(config):
assert bigchain.me == init_kwargs['public_key'] assert bigchain.me == init_kwargs['public_key']
assert bigchain.me_private == init_kwargs['private_key'] assert bigchain.me_private == init_kwargs['private_key']
assert bigchain.federation_nodes == init_kwargs['keyring'] assert bigchain.federation_nodes == init_kwargs['keyring']
assert bigchain.consensus == BaseConsensusRules
assert bigchain._conn is None assert bigchain._conn is None

View File

@ -37,3 +37,32 @@ def test_bigchain_instance_raises_when_not_configured(monkeypatch):
with pytest.raises(exceptions.KeypairNotFoundException): with pytest.raises(exceptions.KeypairNotFoundException):
bigchaindb.Bigchain() bigchaindb.Bigchain()
def test_load_consensus_plugin_loads_default_rules_without_name():
from bigchaindb import config_utils
from bigchaindb.consensus import BaseConsensusRules
assert config_utils.load_consensus_plugin() == BaseConsensusRules
def test_load_consensus_plugin_raises_with_unknown_name():
from pkg_resources import ResolutionError
from bigchaindb import config_utils
with pytest.raises(ResolutionError):
config_utils.load_consensus_plugin('bogus')
def test_load_consensus_plugin_raises_with_invalid_subclass(monkeypatch):
# Monkeypatch entry_point.load to return something other than a
# ConsensusRules instance
from bigchaindb import config_utils
monkeypatch.setattr(config_utils,
'iter_entry_points',
lambda *args: [ type('entry_point',
(object),
{'load': lambda: object}) ])
with pytest.raises(TypeError):
config_utils.load_consensus_plugin()