mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Problem: Cannot detect transaction type and class dynamically
Solution: Create registry for store transaction types and their corresponding classes
This commit is contained in:
parent
6a00c0823f
commit
5426732090
@ -86,3 +86,11 @@ config = {
|
||||
_config = copy.deepcopy(config)
|
||||
from bigchaindb.tendermint import BigchainDB # noqa
|
||||
from bigchaindb.version import __version__ # noqa
|
||||
from bigchaindb.common.transaction import Transaction # noqa
|
||||
from bigchaindb import models # noqa
|
||||
from bigchaindb.upsert_validator import ValidatorElection # noqa
|
||||
|
||||
|
||||
Transaction.register_type(Transaction.CREATE, models.Transaction)
|
||||
Transaction.register_type(Transaction.TRANSFER, models.Transaction)
|
||||
Transaction.register_type(ValidatorElection.VALIDATOR_ELECTION, ValidatorElection)
|
||||
|
||||
@ -104,5 +104,5 @@ class MultipleValidatorOperationError(ValidationError):
|
||||
"""Raised when a validator update pending but new request is submited"""
|
||||
|
||||
|
||||
class MultipleInputsError(BigchainDBError):
|
||||
class MultipleInputsError(ValidationError):
|
||||
"""Raised if there were multiple inputs when only one was expected"""
|
||||
|
||||
@ -585,6 +585,38 @@ class Transaction(object):
|
||||
def _hash(self):
|
||||
self._id = hash_data(self.serialized)
|
||||
|
||||
@classmethod
|
||||
def validate_create(cls, tx_signers, recipients, asset, metadata):
|
||||
if not isinstance(tx_signers, list):
|
||||
raise TypeError('`tx_signers` must be a list instance')
|
||||
if not isinstance(recipients, list):
|
||||
raise TypeError('`recipients` must be a list instance')
|
||||
if len(tx_signers) == 0:
|
||||
raise ValueError('`tx_signers` list cannot be empty')
|
||||
if len(recipients) == 0:
|
||||
raise ValueError('`recipients` list cannot be empty')
|
||||
if not (asset is None or isinstance(asset, dict)):
|
||||
raise TypeError('`asset` must be a dict or None')
|
||||
if not (metadata is None or isinstance(metadata, dict)):
|
||||
raise TypeError('`metadata` must be a dict or None')
|
||||
|
||||
inputs = []
|
||||
outputs = []
|
||||
|
||||
# generate_outputs
|
||||
for recipient in recipients:
|
||||
if not isinstance(recipient, tuple) or len(recipient) != 2:
|
||||
raise ValueError(('Each `recipient` in the list must be a'
|
||||
' tuple of `([<list of public keys>],'
|
||||
' <amount>)`'))
|
||||
pub_keys, amount = recipient
|
||||
outputs.append(Output.generate(pub_keys, amount))
|
||||
|
||||
# generate inputs
|
||||
inputs.append(Input.generate(tx_signers))
|
||||
|
||||
return (inputs, outputs)
|
||||
|
||||
@classmethod
|
||||
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
||||
"""A simple way to generate a `CREATE` transaction.
|
||||
@ -613,32 +645,8 @@ class Transaction(object):
|
||||
Returns:
|
||||
:class:`~bigchaindb.common.transaction.Transaction`
|
||||
"""
|
||||
if not isinstance(tx_signers, list):
|
||||
raise TypeError('`tx_signers` must be a list instance')
|
||||
if not isinstance(recipients, list):
|
||||
raise TypeError('`recipients` must be a list instance')
|
||||
if len(tx_signers) == 0:
|
||||
raise ValueError('`tx_signers` list cannot be empty')
|
||||
if len(recipients) == 0:
|
||||
raise ValueError('`recipients` list cannot be empty')
|
||||
if not (asset is None or isinstance(asset, dict)):
|
||||
raise TypeError('`asset` must be a dict or None')
|
||||
|
||||
inputs = []
|
||||
outputs = []
|
||||
|
||||
# generate_outputs
|
||||
for recipient in recipients:
|
||||
if not isinstance(recipient, tuple) or len(recipient) != 2:
|
||||
raise ValueError(('Each `recipient` in the list must be a'
|
||||
' tuple of `([<list of public keys>],'
|
||||
' <amount>)`'))
|
||||
pub_keys, amount = recipient
|
||||
outputs.append(Output.generate(pub_keys, amount))
|
||||
|
||||
# generate inputs
|
||||
inputs.append(Input.generate(tx_signers))
|
||||
|
||||
(inputs, outputs) = cls.validate_create(tx_signers, recipients, asset, metadata)
|
||||
return cls(cls.CREATE, {'data': asset}, inputs, outputs, metadata)
|
||||
|
||||
@classmethod
|
||||
@ -1150,7 +1158,7 @@ class Transaction(object):
|
||||
raise InvalidHash(err_msg.format(proposed_tx_id))
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, tx):
|
||||
def from_dict(cls, tx, skip_schema_validation=True):
|
||||
"""Transforms a Python dictionary to a Transaction object.
|
||||
|
||||
Args:
|
||||
@ -1159,6 +1167,11 @@ class Transaction(object):
|
||||
Returns:
|
||||
:class:`~bigchaindb.common.transaction.Transaction`
|
||||
"""
|
||||
operation = tx.get('operation', Transaction.CREATE) if isinstance(tx, dict) else Transaction.CREATE
|
||||
cls = Transaction.resolve_class(operation)
|
||||
if not skip_schema_validation:
|
||||
cls.validate_schema(tx)
|
||||
|
||||
inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
|
||||
outputs = [Output.from_dict(output) for output in tx['outputs']]
|
||||
return cls(tx['operation'], tx['asset'], inputs, outputs,
|
||||
@ -1213,3 +1226,17 @@ class Transaction(object):
|
||||
else:
|
||||
tx = list(tx_map.values())[0]
|
||||
return cls.from_dict(tx)
|
||||
|
||||
type_registry = {}
|
||||
|
||||
@staticmethod
|
||||
def register_type(tx_type, tx_class):
|
||||
Transaction.type_registry[tx_type] = tx_class
|
||||
|
||||
def resolve_class(operation):
|
||||
"""For the given `tx` based on the `operation` key return its implementation class"""
|
||||
|
||||
if operation in list(Transaction.type_registry.keys()):
|
||||
return Transaction.type_registry.get(operation)
|
||||
else:
|
||||
return Transaction.type_registry.get(Transaction.CREATE)
|
||||
|
||||
@ -10,6 +10,7 @@ from bigchaindb.backend.schema import validate_language_key
|
||||
|
||||
|
||||
class Transaction(Transaction):
|
||||
|
||||
def validate(self, bigchain, current_transactions=[]):
|
||||
"""Validate transaction spend
|
||||
|
||||
@ -94,12 +95,15 @@ class Transaction(Transaction):
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, tx_body):
|
||||
super().validate_id(tx_body)
|
||||
return super().from_dict(tx_body, False)
|
||||
|
||||
@classmethod
|
||||
def validate_schema(cls, tx_body):
|
||||
cls.validate_id(tx_body)
|
||||
validate_transaction_schema(tx_body)
|
||||
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
|
||||
validate_txn_obj('metadata', tx_body, 'metadata', validate_key)
|
||||
validate_language_key(tx_body['asset'], 'data')
|
||||
return super().from_dict(tx_body)
|
||||
|
||||
|
||||
class FastTransaction:
|
||||
|
||||
@ -18,7 +18,6 @@ import requests
|
||||
import bigchaindb
|
||||
from bigchaindb import backend, config_utils
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.validation_plugins import operation_class
|
||||
from bigchaindb.common.exceptions import (SchemaValidationError,
|
||||
ValidationError,
|
||||
DoubleSpend)
|
||||
@ -327,7 +326,7 @@ class BigchainDB(object):
|
||||
if len(transactions) + len(current_spent_transactions) > 1:
|
||||
raise DoubleSpend('tx "{}" spends inputs twice'.format(txid))
|
||||
elif transactions:
|
||||
transaction = operation_class(transactions[0]).from_db(self, transactions[0])
|
||||
transaction = Transaction.from_db(self, transactions[0])
|
||||
elif current_spent_transactions:
|
||||
transaction = current_spent_transactions[0]
|
||||
|
||||
@ -365,8 +364,7 @@ class BigchainDB(object):
|
||||
|
||||
if block:
|
||||
transactions = backend.query.get_transactions(self.connection, block['transactions'])
|
||||
cls = operation_class(transactions[0])
|
||||
result['transactions'] = [t.to_dict() for t in cls.from_db(self, transactions)]
|
||||
result['transactions'] = [t.to_dict() for t in Transaction.from_db(self, transactions)]
|
||||
|
||||
return result
|
||||
|
||||
@ -396,7 +394,7 @@ class BigchainDB(object):
|
||||
# throught the code base.
|
||||
if isinstance(transaction, dict):
|
||||
try:
|
||||
transaction = operation_class(tx).from_dict(tx)
|
||||
transaction = Transaction.from_dict(tx)
|
||||
except SchemaValidationError as e:
|
||||
logger.warning('Invalid transaction schema: %s', e.__cause__.message)
|
||||
return False
|
||||
|
||||
@ -2,7 +2,7 @@ from bigchaindb.common.exceptions import (InvalidSignature, MultipleInputsError,
|
||||
DuplicateTransaction)
|
||||
from bigchaindb.tendermint.utils import key_from_base64
|
||||
from bigchaindb.common.crypto import (public_key_from_ed25519_key)
|
||||
from bigchaindb.common.transaction import Transaction, Input, Output
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.schema import (_load_schema,
|
||||
_validate_schema,
|
||||
TX_SCHEMA_VERSION,
|
||||
@ -22,14 +22,12 @@ class ValidatorElection(Transaction):
|
||||
CREATE = VALIDATOR_ELECTION
|
||||
ALLOWED_OPERATIONS = (VALIDATOR_ELECTION,)
|
||||
|
||||
def __init__(self, operation, asset, inputs=None, outputs=None,
|
||||
def __init__(self, operation, asset, inputs, outputs,
|
||||
metadata=None, version=None, hash_id=None):
|
||||
# operation `CREATE` is being passed as argument as `VALIDATOR_ELECTION` is an extension
|
||||
# of `CREATE` and any validation on `CREATE` in the parent class should apply to it
|
||||
super().__init__(operation, asset, inputs, outputs, metadata, version, hash_id)
|
||||
|
||||
self.operation = self.VALIDATOR_ELECTION
|
||||
|
||||
@classmethod
|
||||
def current_validators(cls, bigchain):
|
||||
"""Return a dictionary of validators with key as `public_key` and
|
||||
@ -118,22 +116,18 @@ class ValidatorElection(Transaction):
|
||||
|
||||
@classmethod
|
||||
def generate(cls, initiator, voters, election_data, metadata=None):
|
||||
election = cls.create(initiator, voters, metadata, asset=election_data)
|
||||
cls.validate_schema(election.to_dict())
|
||||
(inputs, outputs) = cls.validate_create(initiator, voters, election_data, metadata)
|
||||
election = cls(cls.VALIDATOR_ELECTION, {'data': election_data}, inputs, outputs, metadata)
|
||||
cls.validate_schema(election.to_dict(), skip_id=True)
|
||||
return election
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, tx):
|
||||
cls.validate_id(tx)
|
||||
# NOTE: The schema validation will ensure that the asset has been properly defined
|
||||
cls.validate_schema(tx)
|
||||
return super().from_dict(tx)
|
||||
|
||||
@classmethod
|
||||
def validate_schema(cls, tx):
|
||||
def validate_schema(cls, tx, skip_id=False):
|
||||
"""Validate the validator election transaction. Since `VALIDATOR_ELECTION` extends `CREATE`
|
||||
transaction, all the validations for `CREATE` transaction should be inherited
|
||||
"""
|
||||
if not skip_id:
|
||||
cls.validate_id(tx)
|
||||
_validate_schema(TX_SCHEMA_COMMON, tx)
|
||||
_validate_schema(TX_SCHEMA_CREATE, tx)
|
||||
_validate_schema(TX_SCHEMA_VALIDATOR_ELECTION, tx)
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.upsert_validator import ValidatorElection
|
||||
|
||||
|
||||
OPERATION_TO_CLASS = {
|
||||
Transaction.CREATE: Transaction,
|
||||
Transaction.TRANSFER: Transaction,
|
||||
ValidatorElection.VALIDATOR_ELECTION: ValidatorElection
|
||||
}
|
||||
|
||||
|
||||
def operation_class(tx):
|
||||
"""For the given `tx` based on the `operation` key return its implementation class"""
|
||||
|
||||
if isinstance(tx, dict):
|
||||
return OPERATION_TO_CLASS.get(tx.get('operation', Transaction.CREATE), Transaction)
|
||||
else:
|
||||
return Transaction
|
||||
@ -10,7 +10,8 @@ from flask_restful import Resource, reqparse
|
||||
from bigchaindb.common.exceptions import SchemaValidationError, ValidationError
|
||||
from bigchaindb.web.views.base import make_error
|
||||
from bigchaindb.web.views import parameters
|
||||
from bigchaindb.validation_plugins import operation_class
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -68,7 +69,7 @@ class TransactionListApi(Resource):
|
||||
tx = request.get_json(force=True)
|
||||
|
||||
try:
|
||||
tx_obj = operation_class(tx).from_dict(tx)
|
||||
tx_obj = Transaction.from_dict(tx)
|
||||
except SchemaValidationError as e:
|
||||
return make_error(
|
||||
400,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user