Pulled validate_transaction and validate_block out into a base ConsensusRules plugin class

This commit is contained in:
Matt Smith 2016-02-29 18:39:23 -08:00
parent 8a9030e5c0
commit ee4720d1a5
2 changed files with 118 additions and 51 deletions

View File

@ -0,0 +1,107 @@
import bigchaindb.exceptions as exceptions
from bigchaindb.crypto import hash_data
class ConsensusRules(object):
"""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.
"""
@classmethod
def validate_transaction(cls, 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(bigchain.serialize(
transaction['transaction']))
if calculated_hash != transaction['id']:
raise exceptions.InvalidHash()
# Check signature
if not bigchain.verify_signature(transaction):
raise exceptions.InvalidSignature()
return transaction
# TODO: check that the votings structure is correctly constructed
@classmethod
def validate_block(cls, 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 and 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(bigchain.serialize(block['block']))
if calculated_hash != block['id']:
raise exceptions.InvalidHash()
return block

View File

@ -327,57 +327,16 @@ 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
# 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: for plugin in self.consensus_plugins:
# check if the input exists, is owned by the current_owner transaction = plugin.validate_transaction(self, transaction)
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']))
# Check hash of the transaction
calculated_hash = hash_data(self.serialize(transaction['transaction']))
if calculated_hash != transaction['id']:
raise exceptions.InvalidHash()
# Check signature
if not self.verify_signature(transaction):
raise exceptions.InvalidSignature()
return 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.
@ -447,12 +406,13 @@ class Bigchain(object):
""" """
# 1. Check if current hash is correct # First run all of the plugin block validation logic
calculated_hash = hash_data(self.serialize(block['block'])) for plugin in self.consensus_plugins:
if calculated_hash != block['id']: transaction = plugin.validate_block(self, block)
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