Merge remote-tracking branch 'origin/master' into add-bft-section-to-docs

This commit is contained in:
troymc 2016-06-29 09:20:19 +02:00
commit f23db12ad1
10 changed files with 175 additions and 47 deletions

View File

@ -1,9 +1,6 @@
import copy
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import bigchaindb.exceptions as exceptions from bigchaindb import crypto, exceptions, util
from bigchaindb import util
from bigchaindb import crypto
class AbstractConsensusRules(metaclass=ABCMeta): class AbstractConsensusRules(metaclass=ABCMeta):
@ -101,7 +98,7 @@ class AbstractConsensusRules(metaclass=ABCMeta):
bool: True if the votes's required signature data is present bool: True if the votes's required signature data is present
and correct, False otherwise. and correct, False otherwise.
""" """
raise NotImplementedError
class BaseConsensusRules(AbstractConsensusRules): class BaseConsensusRules(AbstractConsensusRules):
"""Base consensus rules for Bigchain. """Base consensus rules for Bigchain.

View File

@ -6,18 +6,7 @@ import rethinkdb as r
import rapidjson import rapidjson
import bigchaindb import bigchaindb
from bigchaindb import util from bigchaindb import config_utils, crypto, exceptions, util
from bigchaindb import config_utils
from bigchaindb import exceptions
from bigchaindb import crypto
class GenesisBlockAlreadyExistsError(Exception):
pass
class ImproperVoteError(Exception):
pass
class Bigchain(object): class Bigchain(object):
@ -446,17 +435,22 @@ class Bigchain(object):
block (dict): block to check. block (dict): block to check.
Returns: Returns:
True if this block already has a valid vote from this node, False otherwise. If bool: :const:`True` if this block already has a
there is already a vote, but the vote is invalid, raises an ImproperVoteError valid vote from this node, :const:`False` otherwise.
Raises:
ImproperVoteError: If there is already a vote,
but the vote is invalid.
""" """
if block['votes']: if block['votes']:
for vote in block['votes']: for vote in block['votes']:
if vote['node_pubkey'] == self.me: if vote['node_pubkey'] == self.me:
if util.verify_vote_signature(block, vote): if not util.verify_vote_signature(block, vote):
raise exceptions.ImproperVoteError(
'Block {} already has an incorrectly signed vote from public key {}'
).format(block['id'], self.me)
return True return True
else:
raise ImproperVoteError('Block {block_id} already has an incorrectly signed vote '
'from public key {me}').format(block_id=block['id'], me=self.me)
return False return False
def is_valid_block(self, block): def is_valid_block(self, block):
@ -509,7 +503,7 @@ class Bigchain(object):
blocks_count = r.table('bigchain').count().run(self.conn) blocks_count = r.table('bigchain').count().run(self.conn)
if blocks_count: if blocks_count:
raise GenesisBlockAlreadyExistsError('Cannot create the Genesis block') raise exceptions.GenesisBlockAlreadyExistsError('Cannot create the Genesis block')
payload = {'message': 'Hello World from the BigchainDB'} payload = {'message': 'Hello World from the BigchainDB'}
transaction = self.create_transaction([self.me], [self.me], None, 'GENESIS', payload=payload) transaction = self.create_transaction([self.me], [self.me], None, 'GENESIS', payload=payload)

View File

@ -1,36 +1,54 @@
"""Custom exceptions used in the `bigchaindb` package. """Custom exceptions used in the `bigchaindb` package.
""" """
class OperationError(Exception): class OperationError(Exception):
"""Raised when an operation cannot go through""" """Raised when an operation cannot go through"""
class TransactionDoesNotExist(Exception): class TransactionDoesNotExist(Exception):
"""Raised if the transaction is not in the database""" """Raised if the transaction is not in the database"""
class TransactionOwnerError(Exception): class TransactionOwnerError(Exception):
"""Raised if a user tries to transfer a transaction they don't own""" """Raised if a user tries to transfer a transaction they don't own"""
class DoubleSpend(Exception): class DoubleSpend(Exception):
"""Raised if a double spend is found""" """Raised if a double spend is found"""
class InvalidHash(Exception): class InvalidHash(Exception):
"""Raised if there was an error checking the hash for a particular operation""" """Raised if there was an error checking the hash for a particular operation"""
class InvalidSignature(Exception): class InvalidSignature(Exception):
"""Raised if there was an error checking the signature for a particular operation""" """Raised if there was an error checking the signature for a particular operation"""
class DatabaseAlreadyExists(Exception): class DatabaseAlreadyExists(Exception):
"""Raised when trying to create the database but the db is already there""" """Raised when trying to create the database but the db is already there"""
class DatabaseDoesNotExist(Exception): class DatabaseDoesNotExist(Exception):
"""Raised when trying to delete the database but the db is not there""" """Raised when trying to delete the database but the db is not there"""
class KeypairNotFoundException(Exception): class KeypairNotFoundException(Exception):
"""Raised if operation cannot proceed because the keypair was not given""" """Raised if operation cannot proceed because the keypair was not given"""
class KeypairMismatchException(Exception): class KeypairMismatchException(Exception):
"""Raised if the private key(s) provided for signing don't match any of the curret owner(s)""" """Raised if the private key(s) provided for signing don't match any of the curret owner(s)"""
class StartupError(Exception): class StartupError(Exception):
"""Raised when there is an error starting up the system""" """Raised when there is an error starting up the system"""
class ImproperVoteError(Exception):
"""Raised when an invalid vote is found"""
class GenesisBlockAlreadyExistsError(Exception):
"""Raised when trying to create the already existing genesis block"""

View File

@ -220,14 +220,13 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
inputs = [inputs] inputs = [inputs]
# handle payload # handle payload
data = None if payload is not None and not isinstance(payload, dict):
if isinstance(payload, (dict, type(None))): raise TypeError('`payload` must be an dict instance or None')
data = { data = {
'uuid': str(uuid.uuid4()), 'uuid': str(uuid.uuid4()),
'payload': payload 'payload': payload
} }
else:
raise TypeError('`payload` must be an dict instance or None')
# handle inputs # handle inputs
fulfillments = [] fulfillments = []
@ -397,13 +396,15 @@ def fulfill_threshold_signature_fulfillment(fulfillment, parsed_fulfillment, ful
try: try:
subfulfillment = parsed_fulfillment_copy.get_subcondition_from_vk(current_owner)[0] subfulfillment = parsed_fulfillment_copy.get_subcondition_from_vk(current_owner)[0]
except IndexError: except IndexError:
exceptions.KeypairMismatchException('Public key {} cannot be found in the fulfillment' raise exceptions.KeypairMismatchException(
.format(current_owner)) 'Public key {} cannot be found in the fulfillment'.format(current_owner))
try: try:
subfulfillment.sign(serialize(fulfillment_message), key_pairs[current_owner]) private_key = key_pairs[current_owner]
except KeyError: except KeyError:
raise exceptions.KeypairMismatchException('Public key {} is not a pair to any of the private keys' raise exceptions.KeypairMismatchException(
.format(current_owner)) 'Public key {} is not a pair to any of the private keys'.format(current_owner))
subfulfillment.sign(serialize(fulfillment_message), private_key)
parsed_fulfillment.add_subfulfillment(subfulfillment) parsed_fulfillment.add_subfulfillment(subfulfillment)
return parsed_fulfillment return parsed_fulfillment

View File

@ -92,7 +92,7 @@ setup(
] ]
}, },
install_requires=[ install_requires=[
'rethinkdb==2.3.0', 'rethinkdb~=2.3',
'pysha3==0.3', 'pysha3==0.3',
'pytz==2015.7', 'pytz==2015.7',
'cryptoconditions==0.4.1', 'cryptoconditions==0.4.1',

View File

@ -9,7 +9,6 @@ Tasks:
import pytest import pytest
import rethinkdb as r import rethinkdb as r
import bigchaindb
from bigchaindb import Bigchain from bigchaindb import Bigchain
from bigchaindb.db import get_conn from bigchaindb.db import get_conn
@ -73,6 +72,7 @@ def setup_database(request, node_config):
@pytest.fixture(scope='function', autouse=True) @pytest.fixture(scope='function', autouse=True)
def cleanup_tables(request, node_config): def cleanup_tables(request, node_config):
db_name = node_config['database']['name'] db_name = node_config['database']['name']
def fin(): def fin():
get_conn().repl() get_conn().repl()
try: try:
@ -87,11 +87,12 @@ def cleanup_tables(request, node_config):
@pytest.fixture @pytest.fixture
def inputs(user_vk, amount=1, b=None): def inputs(user_vk, amount=1, b=None):
from bigchaindb.exceptions import GenesisBlockAlreadyExistsError
# 1. create the genesis block # 1. create the genesis block
b = b or Bigchain() b = b or Bigchain()
try: try:
b.create_genesis_block() b.create_genesis_block()
except bigchaindb.core.GenesisBlockAlreadyExistsError: except GenesisBlockAlreadyExistsError:
pass pass
# 2. create block with transactions for `USER` to spend # 2. create block with transactions for `USER` to spend
@ -105,4 +106,3 @@ def inputs(user_vk, amount=1, b=None):
block = b.create_block(transactions) block = b.create_block(transactions)
b.write_block(block, durability='hard') b.write_block(block, durability='hard')
return block return block

View File

@ -8,9 +8,7 @@ import rethinkdb as r
import cryptoconditions as cc import cryptoconditions as cc
import bigchaindb import bigchaindb
from bigchaindb import util from bigchaindb import crypto, exceptions, util
from bigchaindb import exceptions
from bigchaindb import crypto
from bigchaindb.voter import Voter from bigchaindb.voter import Voter
from bigchaindb.block import Block, BlockDeleteRevert from bigchaindb.block import Block, BlockDeleteRevert
@ -178,7 +176,7 @@ class TestBigchainApi(object):
def test_create_genesis_block_fails_if_table_not_empty(self, b): def test_create_genesis_block_fails_if_table_not_empty(self, b):
b.create_genesis_block() b.create_genesis_block()
with pytest.raises(bigchaindb.core.GenesisBlockAlreadyExistsError): with pytest.raises(exceptions.GenesisBlockAlreadyExistsError):
b.create_genesis_block() b.create_genesis_block()
genesis_blocks = list(r.table('bigchain') genesis_blocks = list(r.table('bigchain')

15
tests/test_consensus.py Normal file
View File

@ -0,0 +1,15 @@
import pytest
class TestBaseConsensusRules(object):
def test_validate_transaction(self):
from bigchaindb.consensus import BaseConsensusRules
transaction = {
'transaction': {
'operation': None,
'fulfillments': None,
},
}
with pytest.raises(ValueError):
BaseConsensusRules.validate_transaction(None, transaction)

View File

@ -1,3 +1,7 @@
from collections import namedtuple
from rethinkdb.ast import RqlQuery
import pytest import pytest
@ -58,3 +62,36 @@ def test_bigchain_class_initialization_with_parameters(config):
assert bigchain.nodes_except_me == init_kwargs['keyring'] assert bigchain.nodes_except_me == init_kwargs['keyring']
assert bigchain.consensus == BaseConsensusRules assert bigchain.consensus == BaseConsensusRules
assert bigchain._conn is None assert bigchain._conn is None
def test_get_blocks_status_containing_tx(monkeypatch):
from bigchaindb.core import Bigchain
blocks = [
{'id': 1}, {'id': 2}
]
monkeypatch.setattr(
Bigchain, 'search_block_election_on_index', lambda x, y, z: blocks)
monkeypatch.setattr(
Bigchain, 'block_election_status', lambda x, y: Bigchain.BLOCK_VALID)
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.util.verify_vote_signature', lambda block, vote: False)
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
block = {'votes': ({'node_pubkey': 'pubkey'},)}
with pytest.raises(Exception):
bigchain.has_previous_vote(block)
@pytest.mark.parametrize('items,exists', (((0,), True), ((), False)))
def test_transaction_exists(monkeypatch, items, exists):
from bigchaindb.core import Bigchain
monkeypatch.setattr(
RqlQuery, 'run', lambda x, y: namedtuple('response', 'items')(items))
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
assert bigchain.transaction_exists('txid') is exists

View File

@ -1,6 +1,9 @@
from unittest.mock import patch, call
import pytest
import queue import queue
from unittest.mock import patch, call
import pytest
from cryptoconditions import ThresholdSha256Fulfillment
@pytest.fixture @pytest.fixture
@ -142,3 +145,68 @@ def test_process_group_instantiates_and_start_processes(mock_process):
for process in pg.processes: for process in pg.processes:
process.start.assert_called_with() process.start.assert_called_with()
def test_create_tx_with_empty_inputs():
from bigchaindb.util import create_tx
tx = create_tx(None, None, [], None)
assert 'id' in tx
assert 'transaction' in tx
assert 'version' in tx
assert 'fulfillments' in tx['transaction']
assert 'conditions' in tx['transaction']
assert 'operation' in tx['transaction']
assert 'timestamp' in tx['transaction']
assert 'data' in tx['transaction']
assert len(tx['transaction']['fulfillments']) == 1
assert tx['transaction']['fulfillments'][0] == {
'current_owners': [], 'input': None, 'fulfillment': None, 'fid': 0}
def test_fulfill_threshold_signature_fulfillment_pubkey_notfound(monkeypatch):
from bigchaindb.exceptions import KeypairMismatchException
from bigchaindb.util import fulfill_threshold_signature_fulfillment
monkeypatch.setattr(
ThresholdSha256Fulfillment,
'get_subcondition_from_vk',
lambda x, y: []
)
fulfillment = {'current_owners': (None,)}
parsed_fulfillment = ThresholdSha256Fulfillment()
with pytest.raises(KeypairMismatchException):
fulfill_threshold_signature_fulfillment(
fulfillment, parsed_fulfillment, None, None)
def test_fulfill_threshold_signature_fulfillment_wrong_privkeys(monkeypatch):
from bigchaindb.exceptions import KeypairMismatchException
from bigchaindb.util import fulfill_threshold_signature_fulfillment
monkeypatch.setattr(
ThresholdSha256Fulfillment,
'get_subcondition_from_vk',
lambda x, y: (None,)
)
fulfillment = {'current_owners': ('alice-pub-key',)}
parsed_fulfillment = ThresholdSha256Fulfillment()
with pytest.raises(KeypairMismatchException):
fulfill_threshold_signature_fulfillment(
fulfillment, parsed_fulfillment, None, {})
def test_check_hash_and_signature_invalid_hash(monkeypatch):
from bigchaindb.exceptions import InvalidHash
from bigchaindb.util import check_hash_and_signature
transaction = {'id': 'txid'}
monkeypatch.setattr('bigchaindb.util.get_hash_data', lambda tx: 'txhash')
with pytest.raises(InvalidHash):
check_hash_and_signature(transaction)
def test_check_hash_and_signature_invalid_signature(monkeypatch):
from bigchaindb.exceptions import InvalidSignature
from bigchaindb.util import check_hash_and_signature
transaction = {'id': 'txid'}
monkeypatch.setattr('bigchaindb.util.get_hash_data', lambda tx: 'txid')
monkeypatch.setattr(
'bigchaindb.util.validate_fulfillments', lambda tx: False)
with pytest.raises(InvalidSignature):
check_hash_and_signature(transaction)