This large (sorry) commit

1. switches from a composable plugin model to a single-plugin model
2. switches class methods to static methods in the BaseConsensusRules class
3. adds create_transaction, sign_transaction, and verify_transaction to
the plugin API

TODO: If we adopt this model, all references in e.g. client.py to util
methods like `sign_tx` need to be routed through the plugin methods, and
possibly need to be added to the plugin interface.
This commit is contained in:
Matt Smith 2016-03-08 18:33:31 -08:00
parent 14b71537d6
commit a5243e43f6
4 changed files with 159 additions and 95 deletions

View File

@ -20,6 +20,7 @@ import collections
from pkg_resources import iter_entry_points, ResolutionError 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(

View File

@ -19,7 +19,7 @@ class AbstractConsensusRules(metaclass=ABCMeta):
"""Validate a transaction. """Validate a transaction.
Args: Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object. bigchain (Bigchain): an instantiated ``bigchaindb.Bigchain`` object.
transaction (dict): transaction to validate. transaction (dict): transaction to validate.
Returns: Returns:
@ -37,7 +37,7 @@ class AbstractConsensusRules(metaclass=ABCMeta):
"""Validate a block. """Validate a block.
Args: Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object. bigchain (Bigchain): an instantiated ``bigchaindb.Bigchain`` object.
block (dict): block to validate. block (dict): block to validate.
Returns: Returns:
@ -48,19 +48,55 @@ class AbstractConsensusRules(metaclass=ABCMeta):
Descriptive exceptions indicating the reason the block failed. Descriptive exceptions indicating the reason the block failed.
See the `exceptions` module for bigchain-native error classes. See the `exceptions` module for bigchain-native error classes.
""" """
return block raise NotImplementedError
class ConsensusRules(AbstractConsensusRules): @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. """Base consensus rules for Bigchain.
This class can be copied to write your own consensus rules! This class can be copied or overridden to write your own consensus rules!
Note: Consensus plugins will be executed in the order that they're listed in
the bigchain config file.
""" """
@classmethod @staticmethod
def validate_transaction(cls, bigchain, transaction): def validate_transaction(bigchain, transaction):
"""Validate a transaction. """Validate a transaction.
Args: Args:
@ -126,14 +162,14 @@ class AbstractConsensusRules(metaclass=ABCMeta):
raise exceptions.InvalidHash() raise exceptions.InvalidHash()
# Check signature # Check signature
if not bigchain.verify_signature(transaction): if not util.verify_signature(transaction):
raise exceptions.InvalidSignature() raise exceptions.InvalidSignature()
return transaction return transaction
# TODO: check that the votings structure is correctly constructed # TODO: Unsure if a bigchain parameter is really necessary here?
@classmethod @staticmethod
def validate_block(cls, bigchain, block): def validate_block(bigchain, block):
"""Validate a block. """Validate a block.
Args: Args:
@ -154,3 +190,32 @@ class AbstractConsensusRules(metaclass=ABCMeta):
raise exceptions.InvalidHash() raise exceptions.InvalidHash()
return block 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

@ -27,7 +27,7 @@ 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_plugins=['base']): 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.
@ -53,7 +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_plugins = config_utils.get_plugins(consensus_plugins) 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()
@ -70,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'):
@ -111,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
@ -250,11 +252,7 @@ class Bigchain(object):
exception describing the reason why the transaction is invalid. exception describing the reason why the transaction is invalid.
""" """
for plugin in self.consensus_plugins: return self.consensus.validate_transaction(self, transaction)
transaction = plugin.validate_transaction(self, 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.
@ -324,10 +322,8 @@ class Bigchain(object):
describing the reason why the block is invalid. describing the reason why the block is invalid.
""" """
# First run all of the plugin block validation logic # First: Run the plugin block validation logic
for plugin in self.consensus_plugins: self.consensus.validate_block(self, block)
transaction = plugin.validate_block(self, block)
# Finally: Tentative assumption that every blockchain will want to # Finally: Tentative assumption that every blockchain will want to
# validate all transactions in each block # validate all transactions in each block
@ -372,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

View File

@ -67,8 +67,8 @@ setup(
'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.plugins': [ 'bigchaindb.consensus': [
'base=bigchaindb.consensus.base:ConsensusRules' 'default=bigchaindb.consensus:BaseConsensusRules'
] ]
}, },
install_requires=[ install_requires=[