mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00: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:
parent
14b71537d6
commit
a5243e43f6
@ -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(
|
||||||
|
@ -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,109 +48,174 @@ 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:
|
||||||
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:
|
||||||
The transaction if the transaction is valid else it raises an
|
The transaction if the transaction is valid else it raises an
|
||||||
exception describing the reason why the transaction is invalid.
|
exception describing the reason why the transaction is invalid.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
OperationError: if the transaction operation is not supported
|
OperationError: if the transaction operation is not supported
|
||||||
TransactionDoesNotExist: if the input of the transaction is not found
|
TransactionDoesNotExist: if the input of the transaction is not found
|
||||||
TransactionOwnerError: if the new transaction is using an input it doesn't own
|
TransactionOwnerError: if the new transaction is using an input it doesn't own
|
||||||
DoubleSpend: if the transaction is a double spend
|
DoubleSpend: if the transaction is a double spend
|
||||||
InvalidHash: if the hash of the transaction is wrong
|
InvalidHash: if the hash of the transaction is wrong
|
||||||
InvalidSignature: if the signature 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
|
# If the operation is CREATE the transaction should have no inputs and
|
||||||
# should be signed by a federation node
|
# should be signed by a federation node
|
||||||
if transaction['transaction']['operation'] == 'CREATE':
|
if transaction['transaction']['operation'] == 'CREATE':
|
||||||
if transaction['transaction']['input']:
|
if transaction['transaction']['input']:
|
||||||
raise ValueError('A CREATE operation has no inputs')
|
raise ValueError('A CREATE operation has no inputs')
|
||||||
if transaction['transaction']['current_owner'] not in (
|
if transaction['transaction']['current_owner'] not in (
|
||||||
bigchain.federation_nodes + [bigchain.me]):
|
bigchain.federation_nodes + [bigchain.me]):
|
||||||
raise exceptions.OperationError(
|
raise exceptions.OperationError(
|
||||||
'Only federation nodes can use the operation `CREATE`')
|
'Only federation nodes can use the operation `CREATE`')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# check if the input exists, is owned by the current_owner
|
# check if the input exists, is owned by the current_owner
|
||||||
if not transaction['transaction']['input']:
|
if not transaction['transaction']['input']:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Only `CREATE` transactions can have null inputs')
|
'Only `CREATE` transactions can have null inputs')
|
||||||
|
|
||||||
tx_input = bigchain.get_transaction(
|
tx_input = bigchain.get_transaction(
|
||||||
transaction['transaction']['input'])
|
transaction['transaction']['input'])
|
||||||
|
|
||||||
if not tx_input:
|
if not tx_input:
|
||||||
raise exceptions.TransactionDoesNotExist(
|
raise exceptions.TransactionDoesNotExist(
|
||||||
'input `{}` does not exist in the bigchain'.format(
|
'input `{}` does not exist in the bigchain'.format(
|
||||||
transaction['transaction']['input']))
|
transaction['transaction']['input']))
|
||||||
|
|
||||||
if (tx_input['transaction']['new_owner'] !=
|
if (tx_input['transaction']['new_owner'] !=
|
||||||
transaction['transaction']['current_owner']):
|
transaction['transaction']['current_owner']):
|
||||||
raise exceptions.TransactionOwnerError(
|
raise exceptions.TransactionOwnerError(
|
||||||
'current_owner `{}` does not own the input `{}`'.format(
|
'current_owner `{}` does not own the input `{}`'.format(
|
||||||
transaction['transaction']['current_owner'],
|
transaction['transaction']['current_owner'],
|
||||||
transaction['transaction']['input']))
|
transaction['transaction']['input']))
|
||||||
|
|
||||||
# check if the input was already spent by a transaction other than
|
# check if the input was already spent by a transaction other than
|
||||||
# this one.
|
# this one.
|
||||||
spent = bigchain.get_spent(tx_input['id'])
|
spent = bigchain.get_spent(tx_input['id'])
|
||||||
if spent and spent['id'] != transaction['id']:
|
if spent and spent['id'] != transaction['id']:
|
||||||
raise exceptions.DoubleSpend(
|
raise exceptions.DoubleSpend(
|
||||||
'input `{}` was already spent'.format(
|
'input `{}` was already spent'.format(
|
||||||
transaction['transaction']['input']))
|
transaction['transaction']['input']))
|
||||||
|
|
||||||
# Check hash of the transaction
|
# Check hash of the transaction
|
||||||
calculated_hash = hash_data(util.serialize(
|
calculated_hash = hash_data(util.serialize(
|
||||||
transaction['transaction']))
|
transaction['transaction']))
|
||||||
if calculated_hash != transaction['id']:
|
if calculated_hash != transaction['id']:
|
||||||
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:
|
||||||
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:
|
||||||
The block if the block is valid else it raises and exception
|
The block if the block is valid else it raises and exception
|
||||||
describing the reason why the block is invalid.
|
describing the reason why the block is invalid.
|
||||||
|
|
||||||
Raises:
|
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
|
# Check if current hash is correct
|
||||||
calculated_hash = hash_data(util.serialize(block['block']))
|
calculated_hash = hash_data(util.serialize(block['block']))
|
||||||
if calculated_hash != block['id']:
|
if calculated_hash != block['id']:
|
||||||
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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
4
setup.py
4
setup.py
@ -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=[
|
||||||
|
Loading…
x
Reference in New Issue
Block a user