mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge pull request #1243 from bigchaindb/validationerror-refactor
Make ValidationError a superclass of all validation errors and use it
This commit is contained in:
@@ -15,7 +15,3 @@ class OperationError(BackendError):
|
||||
|
||||
class DuplicateKeyError(OperationError):
|
||||
"""Exception raised when an insert fails because the key is not unique"""
|
||||
|
||||
|
||||
class BigchainDBCritical(Exception):
|
||||
"""Unhandleable error that requires attention"""
|
||||
|
||||
@@ -7,44 +7,6 @@ class ConfigurationError(BigchainDBError):
|
||||
"""Raised when there is a problem with server configuration"""
|
||||
|
||||
|
||||
class OperationError(BigchainDBError):
|
||||
"""Raised when an operation cannot go through"""
|
||||
|
||||
|
||||
class TransactionDoesNotExist(BigchainDBError):
|
||||
"""Raised if the transaction is not in the database"""
|
||||
|
||||
|
||||
class TransactionOwnerError(BigchainDBError):
|
||||
"""Raised if a user tries to transfer a transaction they don't own"""
|
||||
|
||||
|
||||
class DoubleSpend(BigchainDBError):
|
||||
"""Raised if a double spend is found"""
|
||||
|
||||
|
||||
class ValidationError(BigchainDBError):
|
||||
"""Raised if there was an error in validation"""
|
||||
|
||||
|
||||
class InvalidHash(ValidationError):
|
||||
"""Raised if there was an error checking the hash for a particular
|
||||
operation"""
|
||||
|
||||
|
||||
class SchemaValidationError(ValidationError):
|
||||
"""Raised if there was any error validating an object's schema"""
|
||||
|
||||
|
||||
class InvalidSignature(BigchainDBError):
|
||||
"""Raised if there was an error checking the signature for a particular
|
||||
operation"""
|
||||
|
||||
|
||||
class DuplicateTransaction(ValidationError):
|
||||
"""Raised if a duplicated transaction is found"""
|
||||
|
||||
|
||||
class DatabaseAlreadyExists(BigchainDBError):
|
||||
"""Raised when trying to create the database but the db is already there"""
|
||||
|
||||
@@ -53,6 +15,18 @@ class DatabaseDoesNotExist(BigchainDBError):
|
||||
"""Raised when trying to delete the database but the db is not there"""
|
||||
|
||||
|
||||
class StartupError(BigchainDBError):
|
||||
"""Raised when there is an error starting up the system"""
|
||||
|
||||
|
||||
class GenesisBlockAlreadyExistsError(BigchainDBError):
|
||||
"""Raised when trying to create the already existing genesis block"""
|
||||
|
||||
|
||||
class CyclicBlockchainError(BigchainDBError):
|
||||
"""Raised when there is a cycle in the blockchain"""
|
||||
|
||||
|
||||
class KeypairNotFoundException(BigchainDBError):
|
||||
"""Raised if operation cannot proceed because the keypair was not given"""
|
||||
|
||||
@@ -62,34 +36,73 @@ class KeypairMismatchException(BigchainDBError):
|
||||
current owner(s)"""
|
||||
|
||||
|
||||
class StartupError(BigchainDBError):
|
||||
"""Raised when there is an error starting up the system"""
|
||||
class OperationError(BigchainDBError):
|
||||
"""Raised when an operation cannot go through"""
|
||||
|
||||
|
||||
class ImproperVoteError(BigchainDBError):
|
||||
################################################################################
|
||||
# Validation errors
|
||||
#
|
||||
# All validation errors (which are handleable errors, not faults) should
|
||||
# subclass ValidationError. However, where possible they should also have their
|
||||
# own distinct type to differentiate them from other validation errors,
|
||||
# especially for the purposes of testing.
|
||||
|
||||
|
||||
class ValidationError(BigchainDBError):
|
||||
"""Raised if there was an error in validation"""
|
||||
|
||||
|
||||
class DoubleSpend(ValidationError):
|
||||
"""Raised if a double spend is found"""
|
||||
|
||||
|
||||
class InvalidHash(ValidationError):
|
||||
"""Raised if there was an error checking the hash for a particular
|
||||
operation"""
|
||||
|
||||
|
||||
class SchemaValidationError(ValidationError):
|
||||
"""Raised if there was any error validating an object's schema"""
|
||||
|
||||
|
||||
class InvalidSignature(ValidationError):
|
||||
"""Raised if there was an error checking the signature for a particular
|
||||
operation"""
|
||||
|
||||
|
||||
class ImproperVoteError(ValidationError):
|
||||
"""Raised if a vote is not constructed correctly, or signed incorrectly"""
|
||||
|
||||
|
||||
class MultipleVotesError(BigchainDBError):
|
||||
class MultipleVotesError(ValidationError):
|
||||
"""Raised if a voter has voted more than once"""
|
||||
|
||||
|
||||
class GenesisBlockAlreadyExistsError(BigchainDBError):
|
||||
"""Raised when trying to create the already existing genesis block"""
|
||||
|
||||
|
||||
class CyclicBlockchainError(BigchainDBError):
|
||||
"""Raised when there is a cycle in the blockchain"""
|
||||
|
||||
|
||||
class TransactionNotInValidBlock(BigchainDBError):
|
||||
class TransactionNotInValidBlock(ValidationError):
|
||||
"""Raised when a transfer transaction is attempting to fulfill the
|
||||
outputs of a transaction that is in an invalid or undecided block"""
|
||||
|
||||
|
||||
class AssetIdMismatch(BigchainDBError):
|
||||
class AssetIdMismatch(ValidationError):
|
||||
"""Raised when multiple transaction inputs related to different assets"""
|
||||
|
||||
|
||||
class AmountError(BigchainDBError):
|
||||
class AmountError(ValidationError):
|
||||
"""Raised when there is a problem with a transaction's output amounts"""
|
||||
|
||||
|
||||
class InputDoesNotExist(ValidationError):
|
||||
"""Raised if a transaction input does not exist"""
|
||||
|
||||
|
||||
class TransactionOwnerError(ValidationError):
|
||||
"""Raised if a user tries to transfer a transaction they don't own"""
|
||||
|
||||
|
||||
class SybilError(ValidationError):
|
||||
"""If a block or vote comes from an unidentifiable node"""
|
||||
|
||||
|
||||
class DuplicateTransaction(ValidationError):
|
||||
"""Raised if a duplicated transaction is found"""
|
||||
|
||||
@@ -4,6 +4,7 @@ import collections
|
||||
from time import time
|
||||
|
||||
from itertools import compress
|
||||
from bigchaindb import exceptions as core_exceptions
|
||||
from bigchaindb.common import crypto, exceptions
|
||||
from bigchaindb.common.utils import gen_timestamp, serialize
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
@@ -11,7 +12,6 @@ from bigchaindb.common.transaction import TransactionLink
|
||||
import bigchaindb
|
||||
|
||||
from bigchaindb import backend, config_utils, utils
|
||||
from bigchaindb.backend import exceptions as backend_exceptions
|
||||
from bigchaindb.consensus import BaseConsensusRules
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
@@ -162,31 +162,6 @@ class Bigchain(object):
|
||||
|
||||
return self.consensus.validate_transaction(self, transaction)
|
||||
|
||||
def is_valid_transaction(self, transaction):
|
||||
"""Check whether a transaction is valid or invalid.
|
||||
|
||||
Similar to :meth:`~bigchaindb.Bigchain.validate_transaction`
|
||||
but never raises an exception. It returns :obj:`False` if
|
||||
the transaction is invalid.
|
||||
|
||||
Args:
|
||||
transaction (:Class:`~bigchaindb.models.Transaction`): transaction
|
||||
to check.
|
||||
|
||||
Returns:
|
||||
The :class:`~bigchaindb.models.Transaction` instance if valid,
|
||||
otherwise :obj:`False`.
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.validate_transaction(transaction)
|
||||
except (ValueError, exceptions.OperationError,
|
||||
exceptions.TransactionDoesNotExist,
|
||||
exceptions.TransactionOwnerError, exceptions.DoubleSpend,
|
||||
exceptions.InvalidHash, exceptions.InvalidSignature,
|
||||
exceptions.TransactionNotInValidBlock, exceptions.AmountError):
|
||||
return False
|
||||
|
||||
def is_new_transaction(self, txid, exclude_block_id=None):
|
||||
"""
|
||||
Return True if the transaction does not exist in any
|
||||
@@ -333,7 +308,7 @@ class Bigchain(object):
|
||||
if list(validity.values()).count(Bigchain.BLOCK_VALID) > 1:
|
||||
block_ids = str([block for block in validity
|
||||
if validity[block] == Bigchain.BLOCK_VALID])
|
||||
raise backend_exceptions.BigchainDBCritical(
|
||||
raise core_exceptions.CriticalDoubleInclusion(
|
||||
'Transaction {tx} is present in '
|
||||
'multiple valid blocks: {block_ids}'
|
||||
.format(tx=txid, block_ids=block_ids))
|
||||
@@ -386,10 +361,9 @@ class Bigchain(object):
|
||||
if self.get_transaction(transaction['id']):
|
||||
num_valid_transactions += 1
|
||||
if num_valid_transactions > 1:
|
||||
raise exceptions.DoubleSpend(('`{}` was spent more than'
|
||||
' once. There is a problem'
|
||||
' with the chain')
|
||||
.format(txid))
|
||||
raise core_exceptions.CriticalDoubleSpend(
|
||||
'`{}` was spent more than once. There is a problem'
|
||||
' with the chain'.format(txid))
|
||||
|
||||
if num_valid_transactions:
|
||||
return Transaction.from_dict(transactions[0])
|
||||
|
||||
@@ -1,2 +1,10 @@
|
||||
class BigchainDBError(Exception):
|
||||
"""Base class for BigchainDB exceptions."""
|
||||
|
||||
|
||||
class CriticalDoubleSpend(BigchainDBError):
|
||||
"""Data integrity error that requires attention"""
|
||||
|
||||
|
||||
class CriticalDoubleInclusion(BigchainDBError):
|
||||
"""Data integrity error that requires attention"""
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from bigchaindb.common.crypto import hash_data, PublicKey, PrivateKey
|
||||
from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
|
||||
OperationError, DoubleSpend,
|
||||
TransactionDoesNotExist,
|
||||
DoubleSpend, InputDoesNotExist,
|
||||
TransactionNotInValidBlock,
|
||||
AssetIdMismatch, AmountError,
|
||||
SybilError, ValidationError,
|
||||
DuplicateTransaction)
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.utils import gen_timestamp, serialize
|
||||
@@ -23,19 +23,10 @@ class Transaction(Transaction):
|
||||
invalid.
|
||||
|
||||
Raises:
|
||||
OperationError: if the transaction operation is not supported
|
||||
TransactionDoesNotExist: if the input of the transaction is not
|
||||
found
|
||||
TransactionNotInValidBlock: if the input of the transaction is not
|
||||
in a valid block
|
||||
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
|
||||
ValidationError: If the transaction is invalid
|
||||
"""
|
||||
if len(self.inputs) == 0:
|
||||
raise ValueError('Transaction contains no inputs')
|
||||
raise ValidationError('Transaction contains no inputs')
|
||||
|
||||
input_conditions = []
|
||||
inputs_defined = all([input_.fulfills for input_ in self.inputs])
|
||||
@@ -47,20 +38,20 @@ class Transaction(Transaction):
|
||||
if self.operation in (Transaction.CREATE, Transaction.GENESIS):
|
||||
# validate asset
|
||||
if self.asset['data'] is not None and not isinstance(self.asset['data'], dict):
|
||||
raise TypeError(('`asset.data` must be a dict instance or '
|
||||
'None for `CREATE` transactions'))
|
||||
raise ValidationError(('`asset.data` must be a dict instance or '
|
||||
'None for `CREATE` transactions'))
|
||||
# validate inputs
|
||||
if inputs_defined:
|
||||
raise ValueError('A CREATE operation has no inputs')
|
||||
raise ValidationError('A CREATE operation has no inputs')
|
||||
elif self.operation == Transaction.TRANSFER:
|
||||
# validate asset
|
||||
if not isinstance(self.asset['id'], str):
|
||||
raise ValueError(('`asset.id` must be a string for '
|
||||
'`TRANSFER` transations'))
|
||||
raise ValidationError('`asset.id` must be a string for '
|
||||
'`TRANSFER` transations')
|
||||
# check inputs
|
||||
if not inputs_defined:
|
||||
raise ValueError('Only `CREATE` transactions can have null '
|
||||
'inputs')
|
||||
raise ValidationError('Only `CREATE` transactions can have '
|
||||
'null inputs')
|
||||
|
||||
# store the inputs so that we can check if the asset ids match
|
||||
input_txs = []
|
||||
@@ -70,8 +61,8 @@ class Transaction(Transaction):
|
||||
get_transaction(input_txid, include_status=True)
|
||||
|
||||
if input_tx is None:
|
||||
raise TransactionDoesNotExist("input `{}` doesn't exist"
|
||||
.format(input_txid))
|
||||
raise InputDoesNotExist("input `{}` doesn't exist"
|
||||
.format(input_txid))
|
||||
|
||||
if status != bigchain.TX_VALID:
|
||||
raise TransactionNotInValidBlock(
|
||||
@@ -117,8 +108,8 @@ class Transaction(Transaction):
|
||||
|
||||
else:
|
||||
allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
|
||||
raise TypeError('`operation`: `{}` must be either {}.'
|
||||
.format(self.operation, allowed_operations))
|
||||
raise ValidationError('`operation`: `{}` must be either {}.'
|
||||
.format(self.operation, allowed_operations))
|
||||
|
||||
if not self.inputs_valid(input_conditions):
|
||||
raise InvalidSignature('Transaction signature is invalid.')
|
||||
@@ -206,18 +197,8 @@ class Block(object):
|
||||
raised.
|
||||
|
||||
Raises:
|
||||
OperationError: If a non-federation node signed the Block.
|
||||
InvalidSignature: If a Block's signature is invalid or if the
|
||||
block contains a transaction with an invalid signature.
|
||||
OperationError: if the transaction operation is not supported
|
||||
TransactionDoesNotExist: if the input of the transaction is not
|
||||
found
|
||||
TransactionNotInValidBlock: if the input of the transaction is not
|
||||
in a valid block
|
||||
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
|
||||
ValidationError: If the block or any transaction in the block does
|
||||
not validate
|
||||
"""
|
||||
|
||||
self._validate_block(bigchain)
|
||||
@@ -233,13 +214,12 @@ class Block(object):
|
||||
object.
|
||||
|
||||
Raises:
|
||||
OperationError: If a non-federation node signed the Block.
|
||||
InvalidSignature: If a Block's signature is invalid.
|
||||
ValidationError: If there is a problem with the block
|
||||
"""
|
||||
# Check if the block was created by a federation node
|
||||
possible_voters = (bigchain.nodes_except_me + [bigchain.me])
|
||||
if self.node_pubkey not in possible_voters:
|
||||
raise OperationError('Only federation nodes can create blocks')
|
||||
raise SybilError('Only federation nodes can create blocks')
|
||||
|
||||
# Check that the signature is valid
|
||||
if not self.is_signature_valid():
|
||||
@@ -252,17 +232,7 @@ class Block(object):
|
||||
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
|
||||
|
||||
Raises:
|
||||
OperationError: if the transaction operation is not supported
|
||||
TransactionDoesNotExist: if the input of the transaction is not
|
||||
found
|
||||
TransactionNotInValidBlock: if the input of the transaction is not
|
||||
in a valid block
|
||||
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
|
||||
DuplicateTransaction: If the block contains a duplicated TX
|
||||
ValidationError: If an invalid transaction is found
|
||||
"""
|
||||
txids = [tx.id for tx in self.transactions]
|
||||
if len(txids) != len(set(txids)):
|
||||
@@ -347,10 +317,10 @@ class Block(object):
|
||||
dict: The Block as a dict.
|
||||
|
||||
Raises:
|
||||
OperationError: If the Block doesn't contain any transactions.
|
||||
ValueError: If the Block doesn't contain any transactions.
|
||||
"""
|
||||
if len(self.transactions) == 0:
|
||||
raise OperationError('Empty block creation is not allowed')
|
||||
raise ValueError('Empty block creation is not allowed')
|
||||
|
||||
block = {
|
||||
'timestamp': self.timestamp,
|
||||
|
||||
@@ -13,8 +13,7 @@ import bigchaindb
|
||||
from bigchaindb import backend
|
||||
from bigchaindb.backend.changefeed import ChangeFeed
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.exceptions import (SchemaValidationError, InvalidHash,
|
||||
InvalidSignature, AmountError)
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
from bigchaindb import Bigchain
|
||||
|
||||
|
||||
@@ -63,8 +62,7 @@ class BlockPipeline:
|
||||
"""
|
||||
try:
|
||||
tx = Transaction.from_dict(tx)
|
||||
except (SchemaValidationError, InvalidHash, InvalidSignature,
|
||||
AmountError):
|
||||
except ValidationError:
|
||||
return None
|
||||
|
||||
# If transaction is in any VALID or UNDECIDED block we
|
||||
@@ -74,12 +72,14 @@ class BlockPipeline:
|
||||
return None
|
||||
|
||||
# If transaction is not valid it should not be included
|
||||
if not self.bigchain.is_valid_transaction(tx):
|
||||
try:
|
||||
tx.validate(self.bigchain)
|
||||
return tx
|
||||
except ValidationError as e:
|
||||
logger.warning('Invalid tx: %s', e)
|
||||
self.bigchain.delete_transaction(tx.id)
|
||||
return None
|
||||
|
||||
return tx
|
||||
|
||||
def create(self, tx, timeout=False):
|
||||
"""Create a block.
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class Vote:
|
||||
return block['id'], [self.invalid_dummy_tx]
|
||||
try:
|
||||
block._validate_block(self.bigchain)
|
||||
except (exceptions.OperationError, exceptions.InvalidSignature):
|
||||
except exceptions.ValidationError:
|
||||
# XXX: if a block is invalid we should skip the `validate_tx`
|
||||
# step, but since we are in a pipeline we cannot just jump to
|
||||
# another function. Hackish solution: generate an invalid
|
||||
@@ -105,7 +105,13 @@ class Vote:
|
||||
if not new:
|
||||
return False, block_id, num_tx
|
||||
|
||||
valid = bool(self.bigchain.is_valid_transaction(tx))
|
||||
try:
|
||||
tx.validate(self.bigchain)
|
||||
valid = True
|
||||
except exceptions.ValidationError as e:
|
||||
logger.warning('Invalid tx: %s', e)
|
||||
valid = False
|
||||
|
||||
return valid, block_id, num_tx
|
||||
|
||||
def vote(self, tx_validity, block_id, num_tx):
|
||||
|
||||
@@ -9,20 +9,7 @@ import logging
|
||||
from flask import current_app, request
|
||||
from flask_restful import Resource, reqparse
|
||||
|
||||
|
||||
from bigchaindb.common.exceptions import (
|
||||
AmountError,
|
||||
DoubleSpend,
|
||||
InvalidHash,
|
||||
InvalidSignature,
|
||||
SchemaValidationError,
|
||||
OperationError,
|
||||
TransactionDoesNotExist,
|
||||
TransactionOwnerError,
|
||||
TransactionNotInValidBlock,
|
||||
ValidationError,
|
||||
)
|
||||
|
||||
from bigchaindb.common.exceptions import SchemaValidationError, ValidationError
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.web.views.base import make_error
|
||||
from bigchaindb.web.views import parameters
|
||||
@@ -84,7 +71,7 @@ class TransactionListApi(Resource):
|
||||
message='Invalid transaction schema: {}'.format(
|
||||
e.__cause__.message)
|
||||
)
|
||||
except (ValidationError, InvalidSignature) as e:
|
||||
except ValidationError as e:
|
||||
return make_error(
|
||||
400,
|
||||
'Invalid transaction ({}): {}'.format(type(e).__name__, e)
|
||||
@@ -93,15 +80,7 @@ class TransactionListApi(Resource):
|
||||
with pool() as bigchain:
|
||||
try:
|
||||
bigchain.validate_transaction(tx_obj)
|
||||
except (ValueError,
|
||||
OperationError,
|
||||
TransactionDoesNotExist,
|
||||
TransactionOwnerError,
|
||||
DoubleSpend,
|
||||
InvalidHash,
|
||||
InvalidSignature,
|
||||
TransactionNotInValidBlock,
|
||||
AmountError) as e:
|
||||
except ValidationError as e:
|
||||
return make_error(
|
||||
400,
|
||||
'Invalid transaction ({}): {}'.format(type(e).__name__, e)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
import pytest
|
||||
import random
|
||||
|
||||
@@ -26,7 +27,7 @@ def test_validate_bad_asset_creation(b, user_pk):
|
||||
tx.asset['data'] = 'a'
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
with pytest.raises(ValidationError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
|
||||
@@ -108,4 +109,4 @@ def test_create_valid_divisible_asset(b, user_pk, user_sk):
|
||||
|
||||
tx = Transaction.create([user_pk], [([user_pk], 2)])
|
||||
tx_signed = tx.sign([user_sk])
|
||||
assert b.is_valid_transaction(tx_signed)
|
||||
tx_signed.validate(b)
|
||||
|
||||
@@ -3,6 +3,8 @@ from time import sleep
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
|
||||
pytestmark = pytest.mark.bdb
|
||||
|
||||
|
||||
@@ -91,7 +93,7 @@ class TestBigchainApi(object):
|
||||
|
||||
@pytest.mark.genesis
|
||||
def test_get_spent_with_double_inclusion_detected(self, b, monkeypatch):
|
||||
from bigchaindb.backend.exceptions import BigchainDBCritical
|
||||
from bigchaindb.exceptions import CriticalDoubleInclusion
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
@@ -121,12 +123,47 @@ class TestBigchainApi(object):
|
||||
vote = b.vote(block3.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
with pytest.raises(BigchainDBCritical):
|
||||
with pytest.raises(CriticalDoubleInclusion):
|
||||
b.get_spent(tx.id, 0)
|
||||
|
||||
@pytest.mark.genesis
|
||||
def test_get_spent_with_double_spend_detected(self, b, monkeypatch):
|
||||
from bigchaindb.exceptions import CriticalDoubleSpend
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1000000000)
|
||||
block1 = b.create_block([tx])
|
||||
b.write_block(block1)
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1000000020)
|
||||
transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
|
||||
asset_id=tx.id)
|
||||
transfer_tx = transfer_tx.sign([b.me_private])
|
||||
block2 = b.create_block([transfer_tx])
|
||||
b.write_block(block2)
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1000000030)
|
||||
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 2)],
|
||||
asset_id=tx.id)
|
||||
transfer_tx2 = transfer_tx2.sign([b.me_private])
|
||||
block3 = b.create_block([transfer_tx2])
|
||||
b.write_block(block3)
|
||||
|
||||
# Vote both block2 and block3 valid
|
||||
vote = b.vote(block2.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
vote = b.vote(block3.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
with pytest.raises(CriticalDoubleSpend):
|
||||
b.get_spent(tx.id, 0)
|
||||
|
||||
@pytest.mark.genesis
|
||||
def test_get_block_status_for_tx_with_double_inclusion(self, b, monkeypatch):
|
||||
from bigchaindb.backend.exceptions import BigchainDBCritical
|
||||
from bigchaindb.exceptions import CriticalDoubleInclusion
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
@@ -146,7 +183,7 @@ class TestBigchainApi(object):
|
||||
vote = b.vote(block2.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
with pytest.raises(BigchainDBCritical):
|
||||
with pytest.raises(CriticalDoubleInclusion):
|
||||
b.get_blocks_status_containing_tx(tx.id)
|
||||
|
||||
@pytest.mark.genesis
|
||||
@@ -530,7 +567,7 @@ class TestBigchainApi(object):
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_non_create_input_not_found(self, b, user_pk):
|
||||
from cryptoconditions import Ed25519Fulfillment
|
||||
from bigchaindb.common.exceptions import TransactionDoesNotExist
|
||||
from bigchaindb.common.exceptions import InputDoesNotExist
|
||||
from bigchaindb.common.transaction import Input, TransactionLink
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb import Bigchain
|
||||
@@ -542,7 +579,7 @@ class TestBigchainApi(object):
|
||||
tx = Transaction.transfer([input], [([user_pk], 1)],
|
||||
asset_id='mock_asset_link')
|
||||
|
||||
with pytest.raises(TransactionDoesNotExist):
|
||||
with pytest.raises(InputDoesNotExist):
|
||||
tx.validate(Bigchain())
|
||||
|
||||
def test_count_backlog(self, b, user_pk):
|
||||
@@ -565,24 +602,24 @@ class TestTransactionValidation(object):
|
||||
# Manipulate input so that it has a `fulfills` defined even
|
||||
# though it shouldn't have one
|
||||
create_tx.inputs[0].fulfills = TransactionLink('abc', 0)
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValidationError) as excinfo:
|
||||
b.validate_transaction(create_tx)
|
||||
assert excinfo.value.args[0] == 'A CREATE operation has no inputs'
|
||||
|
||||
def test_transfer_operation_no_inputs(self, b, user_pk,
|
||||
signed_transfer_tx):
|
||||
signed_transfer_tx.inputs[0].fulfills = None
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValidationError) as excinfo:
|
||||
b.validate_transaction(signed_transfer_tx)
|
||||
|
||||
assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs'
|
||||
|
||||
def test_non_create_input_not_found(self, b, user_pk, signed_transfer_tx):
|
||||
from bigchaindb.common.exceptions import TransactionDoesNotExist
|
||||
from bigchaindb.common.exceptions import InputDoesNotExist
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
|
||||
signed_transfer_tx.inputs[0].fulfills = TransactionLink('c', 0)
|
||||
with pytest.raises(TransactionDoesNotExist):
|
||||
with pytest.raises(InputDoesNotExist):
|
||||
b.validate_transaction(signed_transfer_tx)
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
@@ -741,7 +778,7 @@ class TestBlockValidation(object):
|
||||
b.validate_block(block)
|
||||
|
||||
def test_invalid_node_pubkey(self, b):
|
||||
from bigchaindb.common.exceptions import OperationError
|
||||
from bigchaindb.common.exceptions import SybilError
|
||||
from bigchaindb.common import crypto
|
||||
|
||||
# blocks can only be created by a federation node
|
||||
@@ -758,8 +795,8 @@ class TestBlockValidation(object):
|
||||
# from a non federation node
|
||||
block = block.sign(tmp_sk)
|
||||
|
||||
# check that validate_block raises an OperationError
|
||||
with pytest.raises(OperationError):
|
||||
# check that validate_block raises an SybilError
|
||||
with pytest.raises(SybilError):
|
||||
b.validate_block(block)
|
||||
|
||||
|
||||
@@ -778,7 +815,7 @@ class TestMultipleInputs(object):
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
# validate transaction
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
tx.validate(b)
|
||||
assert len(tx.inputs) == 1
|
||||
assert len(tx.outputs) == 1
|
||||
|
||||
@@ -800,7 +837,7 @@ class TestMultipleInputs(object):
|
||||
asset_id=input_tx.id)
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
tx.validate(b)
|
||||
assert len(tx.inputs) == 1
|
||||
assert len(tx.outputs) == 1
|
||||
|
||||
@@ -832,7 +869,7 @@ class TestMultipleInputs(object):
|
||||
transfer_tx = transfer_tx.sign([user_sk, user2_sk])
|
||||
|
||||
# validate transaction
|
||||
assert b.is_valid_transaction(transfer_tx) == transfer_tx
|
||||
transfer_tx.validate(b)
|
||||
assert len(transfer_tx.inputs) == 1
|
||||
assert len(transfer_tx.outputs) == 1
|
||||
|
||||
@@ -865,7 +902,7 @@ class TestMultipleInputs(object):
|
||||
asset_id=tx_input.id)
|
||||
tx = tx.sign([user_sk, user2_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
tx.validate(b)
|
||||
assert len(tx.inputs) == 1
|
||||
assert len(tx.outputs) == 1
|
||||
|
||||
@@ -1219,7 +1256,6 @@ def test_cant_spend_same_input_twice_in_tx(b, genesis_block):
|
||||
tx_transfer = Transaction.transfer(dup_inputs, [([b.me], 200)],
|
||||
asset_id=tx_create.id)
|
||||
tx_transfer_signed = tx_transfer.sign([b.me_private])
|
||||
assert b.is_valid_transaction(tx_transfer_signed) is False
|
||||
with pytest.raises(DoubleSpend):
|
||||
tx_transfer_signed.validate(b)
|
||||
|
||||
@@ -1277,3 +1313,10 @@ def test_is_new_transaction(b, genesis_block):
|
||||
# Tx is new because it's only found in an invalid block
|
||||
assert b.is_new_transaction(tx.id)
|
||||
assert b.is_new_transaction(tx.id, exclude_block_id=block.id)
|
||||
|
||||
|
||||
def test_validate_asset_id_string(signed_transfer_tx):
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
signed_transfer_tx.asset['id'] = 1
|
||||
with pytest.raises(ValidationError):
|
||||
signed_transfer_tx.validate(None)
|
||||
|
||||
@@ -46,28 +46,19 @@ def test_validate_transaction_handles_exceptions(b, signed_create_tx):
|
||||
"""
|
||||
from bigchaindb.pipelines.block import BlockPipeline
|
||||
block_maker = BlockPipeline()
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
|
||||
# Test SchemaValidationError
|
||||
tx_dict = signed_create_tx.to_dict()
|
||||
tx_dict['invalid_key'] = 'schema validation gonna getcha!'
|
||||
assert block_maker.validate_tx(tx_dict) is None
|
||||
|
||||
# Test InvalidHash
|
||||
tx_dict = signed_create_tx.to_dict()
|
||||
tx_dict['id'] = 'a' * 64
|
||||
assert block_maker.validate_tx(tx_dict) is None
|
||||
with patch('bigchaindb.models.Transaction.validate') as validate:
|
||||
# Assert that validationerror gets caught
|
||||
validate.side_effect = ValidationError()
|
||||
assert block_maker.validate_tx(tx_dict) is None
|
||||
|
||||
# Test InvalidSignature when we pass a bad fulfillment
|
||||
tx_dict = signed_create_tx.to_dict()
|
||||
tx_dict['inputs'][0]['fulfillment'] = 'cf:0:aaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
assert block_maker.validate_tx(tx_dict) is None
|
||||
|
||||
# Test AmountError
|
||||
signed_create_tx.outputs[0].amount = 0
|
||||
tx_dict = signed_create_tx.to_dict()
|
||||
# set the correct value back so that we can continue using it
|
||||
signed_create_tx.outputs[0].amount = 1
|
||||
assert block_maker.validate_tx(tx_dict) is None
|
||||
# Assert that another error doesnt
|
||||
validate.side_effect = IOError()
|
||||
with pytest.raises(IOError):
|
||||
block_maker.validate_tx(tx_dict)
|
||||
|
||||
|
||||
def test_create_block(b, user_pk):
|
||||
|
||||
@@ -128,17 +128,23 @@ def test_validate_block_with_invalid_signature(b):
|
||||
@pytest.mark.genesis
|
||||
def test_vote_validate_transaction(b):
|
||||
from bigchaindb.pipelines import vote
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
|
||||
tx = dummy_tx(b)
|
||||
vote_obj = vote.Vote()
|
||||
validation = vote_obj.validate_tx(tx, 123, 1)
|
||||
assert validation == (True, 123, 1)
|
||||
|
||||
# NOTE: Submit unsigned transaction to `validate_tx` yields `False`.
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
validation = vote_obj.validate_tx(tx, 456, 10)
|
||||
assert validation == (False, 456, 10)
|
||||
with patch('bigchaindb.models.Transaction.validate') as validate:
|
||||
# Assert that validationerror gets caught
|
||||
validate.side_effect = ValidationError()
|
||||
validation = vote_obj.validate_tx(tx, 456, 10)
|
||||
assert validation == (False, 456, 10)
|
||||
|
||||
# Assert that another error doesnt
|
||||
validate.side_effect = IOError()
|
||||
with pytest.raises(IOError):
|
||||
validation = vote_obj.validate_tx(tx, 456, 10)
|
||||
|
||||
|
||||
@pytest.mark.genesis
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pytest import raises
|
||||
from bigchaindb.common.exceptions import ValidationError
|
||||
|
||||
|
||||
class TestTransactionModel(object):
|
||||
@@ -8,12 +9,12 @@ class TestTransactionModel(object):
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx.operation = 'something invalid'
|
||||
|
||||
with raises(TypeError):
|
||||
with raises(ValidationError):
|
||||
tx.validate(b)
|
||||
|
||||
tx.operation = 'CREATE'
|
||||
tx.inputs = []
|
||||
with raises(ValueError):
|
||||
with raises(ValidationError):
|
||||
tx.validate(b)
|
||||
|
||||
|
||||
@@ -61,11 +62,10 @@ class TestBlockModel(object):
|
||||
assert block.to_dict() == expected
|
||||
|
||||
def test_block_invalid_serializaton(self):
|
||||
from bigchaindb.common.exceptions import OperationError
|
||||
from bigchaindb.models import Block
|
||||
|
||||
block = Block([])
|
||||
with raises(OperationError):
|
||||
with raises(ValueError):
|
||||
block.to_dict()
|
||||
|
||||
def test_block_deserialization(self, b):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import builtins
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -113,18 +112,15 @@ def test_post_create_transaction_with_invalid_schema(client, caplog):
|
||||
('DoubleSpend', 'Nope! It is gone now!'),
|
||||
('InvalidHash', 'Do not smoke that!'),
|
||||
('InvalidSignature', 'Falsche Unterschrift!'),
|
||||
('OperationError', 'Create and transfer!'),
|
||||
('TransactionDoesNotExist', 'Hallucinations?'),
|
||||
('ValidationError', 'Create and transfer!'),
|
||||
('InputDoesNotExist', 'Hallucinations?'),
|
||||
('TransactionOwnerError', 'Not yours!'),
|
||||
('TransactionNotInValidBlock', 'Wait, maybe?'),
|
||||
('ValueError', '?'),
|
||||
('ValidationError', '?'),
|
||||
))
|
||||
def test_post_invalid_transaction(client, exc, msg, monkeypatch, caplog):
|
||||
from bigchaindb.common import exceptions
|
||||
try:
|
||||
exc_cls = getattr(exceptions, exc)
|
||||
except AttributeError:
|
||||
exc_cls = getattr(builtins, 'ValueError')
|
||||
exc_cls = getattr(exceptions, exc)
|
||||
|
||||
def mock_validation(self_, tx):
|
||||
raise exc_cls(msg)
|
||||
|
||||
Reference in New Issue
Block a user