mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge branch 'master' into voting-class-integration
This commit is contained in:
parent
e011f50bc7
commit
dc58466de3
@ -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,15 +15,6 @@ class DatabaseDoesNotExist(BigchainDBError):
|
||||
"""Raised when trying to delete the database but the db is not there"""
|
||||
|
||||
|
||||
class KeypairNotFoundException(BigchainDBError):
|
||||
"""Raised if operation cannot proceed because the keypair was not given"""
|
||||
|
||||
|
||||
class KeypairMismatchException(BigchainDBError):
|
||||
"""Raised if the private key(s) provided for signing don't match any of the
|
||||
current owner(s)"""
|
||||
|
||||
|
||||
class StartupError(BigchainDBError):
|
||||
"""Raised when there is an error starting up the system"""
|
||||
|
||||
@ -74,14 +27,82 @@ class CyclicBlockchainError(BigchainDBError):
|
||||
"""Raised when there is a cycle in the blockchain"""
|
||||
|
||||
|
||||
class TransactionNotInValidBlock(BigchainDBError):
|
||||
class KeypairNotFoundException(BigchainDBError):
|
||||
"""Raised if operation cannot proceed because the keypair was not given"""
|
||||
|
||||
|
||||
class KeypairMismatchException(BigchainDBError):
|
||||
"""Raised if the private key(s) provided for signing don't match any of the
|
||||
current owner(s)"""
|
||||
|
||||
|
||||
class OperationError(BigchainDBError):
|
||||
"""Raised when an operation cannot go through"""
|
||||
|
||||
|
||||
################################################################################
|
||||
# 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(ValidationError):
|
||||
"""Raised if a voter has voted more than once"""
|
||||
|
||||
|
||||
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"""
|
||||
|
@ -1,6 +1,7 @@
|
||||
import random
|
||||
from time import time
|
||||
|
||||
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
|
||||
@ -8,7 +9,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
|
||||
|
||||
@ -110,7 +110,9 @@ class Bigchain(object):
|
||||
dict: database response or None if no reassignment is possible
|
||||
"""
|
||||
|
||||
other_nodes = self.federation.difference([transaction['assignee']])
|
||||
other_nodes = tuple(
|
||||
self.federation.difference([transaction['assignee']])
|
||||
)
|
||||
new_assignee = random.choice(other_nodes) if other_nodes else self.me
|
||||
|
||||
return backend.query.update_transaction(
|
||||
@ -151,31 +153,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
|
||||
@ -317,7 +294,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))
|
||||
@ -370,10 +347,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,15 +214,14 @@ 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
|
||||
if self.node_pubkey not in bigchain.federation:
|
||||
raise OperationError('Only federation nodes can create blocks')
|
||||
raise SybilError('Only federation nodes can create blocks')
|
||||
|
||||
if set(self.voters) != bigchain.federation:
|
||||
raise OperationError('Block voters differs from server keyring')
|
||||
raise SybilError('Block voters differs from server keyring')
|
||||
|
||||
# Check that the signature is valid
|
||||
if not self.is_signature_valid():
|
||||
@ -254,17 +234,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)):
|
||||
@ -349,10 +319,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.
|
||||
|
||||
|
@ -60,7 +60,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
|
||||
@ -104,7 +104,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
|
||||
@ -478,7 +515,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
|
||||
@ -490,7 +527,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):
|
||||
@ -513,24 +550,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')
|
||||
@ -689,7 +726,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
|
||||
@ -706,8 +743,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)
|
||||
|
||||
|
||||
@ -726,7 +763,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
|
||||
|
||||
@ -748,7 +785,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
|
||||
|
||||
@ -780,7 +817,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
|
||||
|
||||
@ -813,7 +850,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
|
||||
|
||||
@ -1167,7 +1204,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)
|
||||
|
||||
@ -1225,3 +1261,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):
|
||||
|
@ -36,7 +36,11 @@ def test_reassign_transactions(b, user_pk):
|
||||
|
||||
stm = stale.StaleTransactionMonitor(timeout=0.001,
|
||||
backlog_reassign_delay=0.001)
|
||||
stm.reassign_transactions(tx.to_dict())
|
||||
# This worked previously because transaction['assignee'] was only used if
|
||||
# bigchain.nodes_except_me was not empty.
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict['assignee'] = b.me
|
||||
stm.reassign_transactions(tx_dict)
|
||||
|
||||
# test with federation
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
@ -58,7 +62,7 @@ def test_reassign_transactions(b, user_pk):
|
||||
tx = tx.sign([b.me_private])
|
||||
stm.bigchain.nodes_except_me = ['lol']
|
||||
b.write_transaction(tx)
|
||||
stm.bigchain.nodes_except_me = None
|
||||
stm.bigchain.nodes_except_me = []
|
||||
|
||||
tx = list(query.get_stale_transactions(b.connection, 0))[0]
|
||||
stm.reassign_transactions(tx)
|
||||
|
@ -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
|
||||
|
@ -80,13 +80,3 @@ def test_get_blocks_status_containing_tx(monkeypatch):
|
||||
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
|
||||
with pytest.raises(Exception):
|
||||
bigchain.get_blocks_status_containing_tx('txid')
|
||||
|
||||
|
||||
def test_has_previous_vote(monkeypatch):
|
||||
from bigchaindb.core import Bigchain
|
||||
monkeypatch.setattr(
|
||||
'bigchaindb.utils.verify_vote_signature', lambda voters, vote: False)
|
||||
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
|
||||
block = {'votes': ({'node_pubkey': 'pubkey'},)}
|
||||
with pytest.raises(Exception):
|
||||
bigchain.has_previous_vote(block)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user