diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index 0a69c150..9283913f 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -41,9 +41,7 @@ config = { 'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01) }, 'api_endpoint': 'http://localhost:8008/api/v1', - 'consensus_plugins': [ - 'base' - ] + 'consensus_plugin': e('BIGCHAIN_CONSENSUS_PLUGIN', default='default') } # We need to maintain a backup copy of the original config dict in case diff --git a/bigchaindb/config_utils.py b/bigchaindb/config_utils.py index c273245d..7f312d0c 100644 --- a/bigchaindb/config_utils.py +++ b/bigchaindb/config_utils.py @@ -17,7 +17,7 @@ import json import logging import collections -from pkg_resources import iter_entry_points +from pkg_resources import iter_entry_points, ResolutionError import bigchaindb @@ -103,20 +103,37 @@ def autoconfigure(): logger.warning('Cannot find your config file. Run `bigchaindb configure` to create one') -def get_plugins(plugin_names): - if not plugin_names: - plugin_names = bigchaindb.config.get('consensus_plugins', []) +def load_consensus_plugin(name=None): + """Find and load the chosen consensus plugin. - plugins = [] + Args: + name (string): the name of the entry_point, as advertised in the + setup.py of the providing package. - # It's important to maintain plugin ordering as stated in the config file. - # e.g. Expensive validation tasks should happen after cheap ones. - # - # TODO: We might want to add some sort of priority system, but for now we - # simply assume everything in a given plugin is designed to run at the - # same time. - for name in plugin_names: - for entry_point in iter_entry_points('bigchaindb.plugins', name): - plugins.append(entry_point.load()) + Returns: + an uninstantiated subclass of ``bigchaindb.consensus.AbstractConsensusRules`` + """ + if not name: + name = bigchaindb.config.get('consensus_plugin', 'default') - return plugins + # TODO: This will return the first plugin with group `bigchaindb.plugins` + # 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.plugins` 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 diff --git a/bigchaindb/consensus.py b/bigchaindb/consensus.py index 4bf5fe66..1c0b1aec 100644 --- a/bigchaindb/consensus.py +++ b/bigchaindb/consensus.py @@ -1,17 +1,21 @@ +from abc import ABCMeta, abstractmethod + import bigchaindb.exceptions as exceptions from bigchaindb import util -from bigchaindb.crypto import hash_data +from bigchaindb.crypto import hash_data, PublicKey -# TODO: no real reason to use abc yet, but later we can enforce inheritance from -# this class when loading plugins if that's desirable. -# from abc import ABCMeta -class AbstractConsensusRules: +class AbstractConsensusRules(metaclass=ABCMeta): + """Abstract base class for Bigchain plugins which implement consensus logic. - # TODO: rather than having plugin-authors inherit and override, - # it'd be cleaner to make a `transactionrule` decorator and etc - @classmethod - def validate_transaction(cls, bigchain, transaction): + 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: @@ -26,10 +30,10 @@ class AbstractConsensusRules: Descriptive exceptions indicating the reason the transaction failed. See the `exceptions` module for bigchain-native error classes. """ - return transaction + raise NotImplementedError - @classmethod - def validate_block(cls, bigchain, block): + @abstractmethod + def validate_block(bigchain, block): """Validate a block. Args: