1
0
mirror of https://github.com/bigchaindb/bigchaindb.git synced 2024-06-26 03:06:43 +02:00

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
import bigchaindb
from bigchaindb.consensus import AbstractConsensusRules
logger = logging.getLogger(__name__)
CONFIG_DEFAULT_PATH = os.environ.setdefault(

View File

@ -19,7 +19,7 @@ class AbstractConsensusRules(metaclass=ABCMeta):
"""Validate a transaction.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
bigchain (Bigchain): an instantiated ``bigchaindb.Bigchain`` object.
transaction (dict): transaction to validate.
Returns:
@ -37,7 +37,7 @@ class AbstractConsensusRules(metaclass=ABCMeta):
"""Validate a block.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
bigchain (Bigchain): an instantiated ``bigchaindb.Bigchain`` object.
block (dict): block to validate.
Returns:
@ -48,109 +48,174 @@ class AbstractConsensusRules(metaclass=ABCMeta):
Descriptive exceptions indicating the reason the block failed.
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.
This class can be copied to write your own consensus rules!
Note: Consensus plugins will be executed in the order that they're listed in
the bigchain config file.
This class can be copied or overridden to write your own consensus rules!
"""
@classmethod
def validate_transaction(cls, bigchain, transaction):
@staticmethod
def validate_transaction(bigchain, transaction):
"""Validate a transaction.
Args:
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
transaction (dict): transaction to validate.
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.
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
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`')
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')
# 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'])
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 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']))
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 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']))
transaction['transaction']))
if calculated_hash != transaction['id']:
raise exceptions.InvalidHash()
raise exceptions.InvalidHash()
# Check signature
if not bigchain.verify_signature(transaction):
raise exceptions.InvalidSignature()
if not util.verify_signature(transaction):
raise exceptions.InvalidSignature()
return transaction
# TODO: check that the votings structure is correctly constructed
@classmethod
def validate_block(cls, bigchain, block):
# 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.
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
block (dict): block to validate.
Returns:
The block if the block is valid else it raises and exception
describing the reason why the block is invalid.
The block if the block is valid else it raises and exception
describing the reason why the block is invalid.
Raises:
InvalidHash: if the hash of the block is wrong.
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()
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

@ -27,7 +27,7 @@ class Bigchain(object):
def __init__(self, host=None, port=None, dbname=None,
public_key=None, private_key=None, keyring=[],
consensus_plugins=['base']):
consensus_plugin=None):
"""Initialize the Bigchain instance
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_private = private_key or bigchaindb.config['keypair']['private']
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:
raise exceptions.KeypairNotFoundException()
@ -70,38 +70,40 @@ class Bigchain(object):
return r.connect(host=self.host, port=self.port, db=self.dbname)
@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
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
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):
"""Verify the signature of a transaction.
def verify_signature(self, signed_transaction, *args, **kwargs):
"""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()
# 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)
return self.consensus.verify_signature(
signed_transaction, *args, **kwargs)
@monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate'])
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.
Args:
singed_transaction (dict): transaction with the `signature` included.
signed_transaction (dict): transaction with the `signature` included.
Returns:
dict: database response
@ -250,11 +252,7 @@ class Bigchain(object):
exception describing the reason why the transaction is invalid.
"""
for plugin in self.consensus_plugins:
transaction = plugin.validate_transaction(self, transaction)
return transaction
return self.consensus.validate_transaction(self, transaction)
def is_valid_transaction(self, transaction):
"""Check whether a transacion is valid or invalid.
@ -324,10 +322,8 @@ class Bigchain(object):
describing the reason why the block is invalid.
"""
# First run all of the plugin block validation logic
for plugin in self.consensus_plugins:
transaction = plugin.validate_block(self, block)
# First: Run the plugin block validation logic
self.consensus.validate_block(self, block)
# Finally: Tentative assumption that every blockchain will want to
# 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)
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):
"""Create the genesis block

View File

@ -67,8 +67,8 @@ setup(
'bigchaindb=bigchaindb.commands.bigchain:main',
'bigchaindb-benchmark=bigchaindb.commands.bigchain_benchmark:main'
],
'bigchaindb.plugins': [
'base=bigchaindb.consensus.base:ConsensusRules'
'bigchaindb.consensus': [
'default=bigchaindb.consensus:BaseConsensusRules'
]
},
install_requires=[