From e75798be38e33a8b2bd2795b703ad318b2484b4d Mon Sep 17 00:00:00 2001 From: troymc Date: Tue, 28 Jun 2016 11:29:20 +0200 Subject: [PATCH 1/4] Use the latest rethinkdb 2.3.x driver for Python --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1b755c08..602bd184 100644 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ setup( ] }, install_requires=[ - 'rethinkdb==2.3.0', + 'rethinkdb~=2.3', 'pysha3==0.3', 'pytz==2015.7', 'cryptoconditions==0.4.1', From 681e347e75dac36b9194ee1a38d5045838c79cce Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Tue, 28 Jun 2016 14:18:50 +0200 Subject: [PATCH 2/4] Test util (#396) * Isolate handling of potential KeyError * Add tests for bigchaindb/util.py * Simplify the logic a bit * Raise the exception --- bigchaindb/util.py | 25 ++++++++-------- tests/test_util.py | 72 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 62454be0..c469ec6b 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -220,15 +220,14 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): inputs = [inputs] # handle payload - data = None - if isinstance(payload, (dict, type(None))): - data = { - 'uuid': str(uuid.uuid4()), - 'payload': payload - } - else: + if payload is not None and not isinstance(payload, dict): raise TypeError('`payload` must be an dict instance or None') + data = { + 'uuid': str(uuid.uuid4()), + 'payload': payload + } + # handle inputs fulfillments = [] @@ -397,13 +396,15 @@ def fulfill_threshold_signature_fulfillment(fulfillment, parsed_fulfillment, ful try: subfulfillment = parsed_fulfillment_copy.get_subcondition_from_vk(current_owner)[0] except IndexError: - exceptions.KeypairMismatchException('Public key {} cannot be found in the fulfillment' - .format(current_owner)) + raise exceptions.KeypairMismatchException( + 'Public key {} cannot be found in the fulfillment'.format(current_owner)) try: - subfulfillment.sign(serialize(fulfillment_message), key_pairs[current_owner]) + private_key = key_pairs[current_owner] except KeyError: - raise exceptions.KeypairMismatchException('Public key {} is not a pair to any of the private keys' - .format(current_owner)) + raise exceptions.KeypairMismatchException( + '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) return parsed_fulfillment diff --git a/tests/test_util.py b/tests/test_util.py index aad1af40..a8bb21ec 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,6 +1,9 @@ -from unittest.mock import patch, call -import pytest import queue +from unittest.mock import patch, call + +import pytest + +from cryptoconditions import ThresholdSha256Fulfillment @pytest.fixture @@ -142,3 +145,68 @@ def test_process_group_instantiates_and_start_processes(mock_process): for process in pg.processes: 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) From ac680cf5e98bd7160e927a8ea6aebd01579c1da9 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Tue, 28 Jun 2016 14:19:07 +0200 Subject: [PATCH 3/4] Test consensus (#398) * Remove unused import * Simplify and group the imports * Add extra space (pep 8) * Remove NotImplementedError the class BaseConsensusRules implements verify_vote_signature * Add test module for consensus module --- bigchaindb/consensus.py | 7 ++----- tests/test_consensus.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 tests/test_consensus.py diff --git a/bigchaindb/consensus.py b/bigchaindb/consensus.py index 5317868a..1723b74c 100644 --- a/bigchaindb/consensus.py +++ b/bigchaindb/consensus.py @@ -1,9 +1,6 @@ -import copy from abc import ABCMeta, abstractmethod -import bigchaindb.exceptions as exceptions -from bigchaindb import util -from bigchaindb import crypto +from bigchaindb import crypto, exceptions, util class AbstractConsensusRules(metaclass=ABCMeta): @@ -101,7 +98,7 @@ class AbstractConsensusRules(metaclass=ABCMeta): bool: True if the votes's required signature data is present and correct, False otherwise. """ - raise NotImplementedError + class BaseConsensusRules(AbstractConsensusRules): """Base consensus rules for Bigchain. diff --git a/tests/test_consensus.py b/tests/test_consensus.py new file mode 100644 index 00000000..8f8f9bcd --- /dev/null +++ b/tests/test_consensus.py @@ -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) From 7bd6d485bc63200b877b6ccdb721bd1cd8968421 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Tue, 28 Jun 2016 14:19:22 +0200 Subject: [PATCH 4/4] Test Bigchain.get_blocks_status_containing_tx() (#399) * Test Bigchain.get_blocks_status_containing_tx() exception case * Test Bigchaindb.has_previous_vote() * Add missing blank lines (pep 8) * Group imports * Move ImproperVoteError into .exceptions.py * Simplify logic * Simplify formatting * Imrpove the docstrings a bit * Move GenesisBlockAlreadyExistsError into .exceptions.py * Remove unused import * Add missing blank line * Remove extra blank line * Group imports from same module together * Add test for Bigchain.transaction_exists() --- bigchaindb/core.py | 34 +++++++++++++------------------- bigchaindb/exceptions.py | 18 +++++++++++++++++ tests/db/conftest.py | 6 +++--- tests/db/test_bigchain_api.py | 6 ++---- tests/test_core.py | 37 +++++++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 27 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 3bc9118a..cb511f10 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -6,18 +6,7 @@ import rethinkdb as r import rapidjson import bigchaindb -from bigchaindb import util -from bigchaindb import config_utils -from bigchaindb import exceptions -from bigchaindb import crypto - - -class GenesisBlockAlreadyExistsError(Exception): - pass - - -class ImproperVoteError(Exception): - pass +from bigchaindb import config_utils, crypto, exceptions, util class Bigchain(object): @@ -446,17 +435,22 @@ class Bigchain(object): block (dict): block to check. Returns: - True if this block already has a valid vote from this node, False otherwise. If - there is already a vote, but the vote is invalid, raises an ImproperVoteError + bool: :const:`True` if this block already has a + valid vote from this node, :const:`False` otherwise. + + Raises: + ImproperVoteError: If there is already a vote, + but the vote is invalid. + """ if block['votes']: for vote in block['votes']: if vote['node_pubkey'] == self.me: - if util.verify_vote_signature(block, vote): - 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) + 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 False def is_valid_block(self, block): @@ -509,7 +503,7 @@ class Bigchain(object): blocks_count = r.table('bigchain').count().run(self.conn) 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'} transaction = self.create_transaction([self.me], [self.me], None, 'GENESIS', payload=payload) diff --git a/bigchaindb/exceptions.py b/bigchaindb/exceptions.py index c991cd70..7c437223 100644 --- a/bigchaindb/exceptions.py +++ b/bigchaindb/exceptions.py @@ -1,36 +1,54 @@ """Custom exceptions used in the `bigchaindb` package. """ + class OperationError(Exception): """Raised when an operation cannot go through""" + class TransactionDoesNotExist(Exception): """Raised if the transaction is not in the database""" + class TransactionOwnerError(Exception): """Raised if a user tries to transfer a transaction they don't own""" + class DoubleSpend(Exception): """Raised if a double spend is found""" + class InvalidHash(Exception): """Raised if there was an error checking the hash for a particular operation""" + class InvalidSignature(Exception): """Raised if there was an error checking the signature for a particular operation""" + class DatabaseAlreadyExists(Exception): """Raised when trying to create the database but the db is already there""" + class DatabaseDoesNotExist(Exception): """Raised when trying to delete the database but the db is not there""" + class KeypairNotFoundException(Exception): """Raised if operation cannot proceed because the keypair was not given""" + class KeypairMismatchException(Exception): """Raised if the private key(s) provided for signing don't match any of the curret owner(s)""" + class StartupError(Exception): """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""" diff --git a/tests/db/conftest.py b/tests/db/conftest.py index c59ab43a..c1a0876c 100644 --- a/tests/db/conftest.py +++ b/tests/db/conftest.py @@ -9,7 +9,6 @@ Tasks: import pytest import rethinkdb as r -import bigchaindb from bigchaindb import Bigchain from bigchaindb.db import get_conn @@ -73,6 +72,7 @@ def setup_database(request, node_config): @pytest.fixture(scope='function', autouse=True) def cleanup_tables(request, node_config): db_name = node_config['database']['name'] + def fin(): get_conn().repl() try: @@ -87,11 +87,12 @@ def cleanup_tables(request, node_config): @pytest.fixture def inputs(user_vk, amount=1, b=None): + from bigchaindb.exceptions import GenesisBlockAlreadyExistsError # 1. create the genesis block b = b or Bigchain() try: b.create_genesis_block() - except bigchaindb.core.GenesisBlockAlreadyExistsError: + except GenesisBlockAlreadyExistsError: pass # 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) b.write_block(block, durability='hard') return block - diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 545296b0..bf2d0bba 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -8,9 +8,7 @@ import rethinkdb as r import cryptoconditions as cc import bigchaindb -from bigchaindb import util -from bigchaindb import exceptions -from bigchaindb import crypto +from bigchaindb import crypto, exceptions, util from bigchaindb.voter import Voter 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): b.create_genesis_block() - with pytest.raises(bigchaindb.core.GenesisBlockAlreadyExistsError): + with pytest.raises(exceptions.GenesisBlockAlreadyExistsError): b.create_genesis_block() genesis_blocks = list(r.table('bigchain') diff --git a/tests/test_core.py b/tests/test_core.py index b7af08d2..91dc9ef5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,3 +1,7 @@ +from collections import namedtuple + +from rethinkdb.ast import RqlQuery + import pytest @@ -58,3 +62,36 @@ def test_bigchain_class_initialization_with_parameters(config): assert bigchain.nodes_except_me == init_kwargs['keyring'] assert bigchain.consensus == BaseConsensusRules 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